init
This commit is contained in:
753
main.py
Normal file
753
main.py
Normal file
@@ -0,0 +1,753 @@
|
||||
#!/usr/bin/env python3
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import pystray
|
||||
from PIL import Image, ImageDraw
|
||||
import threading
|
||||
import sys
|
||||
import queue
|
||||
from models import Customer, Location, Host
|
||||
from data_loader import load_customers
|
||||
|
||||
|
||||
class VPNManagerWindow:
|
||||
def __init__(self):
|
||||
self.window = tk.Tk()
|
||||
self.window.title("VPN Manager")
|
||||
self.window.geometry("1200x750")
|
||||
self.window.minsize(1000, 600)
|
||||
self.window.protocol("WM_DELETE_WINDOW", self.hide_window)
|
||||
|
||||
# Command queue for thread communication
|
||||
self.command_queue = queue.Queue()
|
||||
|
||||
# Configure modern theme colors
|
||||
self.bg_color = "#1a1d29" # Deep dark blue
|
||||
self.fg_color = "#e8eaf6" # Soft white
|
||||
self.entry_bg = "#2d3142" # Dark gray-blue
|
||||
self.button_bg = "#252836" # Card background
|
||||
self.hover_bg = "#3a3f5c" # Hover state
|
||||
self.accent_color = "#5e72e4" # Primary accent (blue)
|
||||
self.success_color = "#2dce89" # Green for connected
|
||||
self.danger_color = "#f5365c" # Red for disconnected
|
||||
self.warning_color = "#fb6340" # Orange for warnings
|
||||
|
||||
self.window.configure(bg=self.bg_color)
|
||||
|
||||
# Load customer data using dataclasses
|
||||
self.customers = load_customers()
|
||||
|
||||
self.filtered_customers = self.customers.copy()
|
||||
self.icon = None
|
||||
|
||||
self.setup_ui()
|
||||
self.setup_tray()
|
||||
self.hide_window()
|
||||
|
||||
def setup_ui(self):
|
||||
# Header with gradient effect (simulated with frame)
|
||||
header_frame = tk.Frame(self.window, bg=self.bg_color)
|
||||
header_frame.pack(fill=tk.X, padx=20, pady=(20, 10))
|
||||
|
||||
# Title with icon
|
||||
title_frame = tk.Frame(header_frame, bg=self.bg_color)
|
||||
title_frame.pack(side=tk.TOP)
|
||||
|
||||
# Shield icon next to title
|
||||
icon_label = tk.Label(
|
||||
title_frame,
|
||||
text="🛡️",
|
||||
font=("Segoe UI Emoji", 24),
|
||||
bg=self.bg_color,
|
||||
fg=self.accent_color
|
||||
)
|
||||
icon_label.pack(side=tk.LEFT, padx=(0, 10))
|
||||
|
||||
title_label = tk.Label(
|
||||
title_frame,
|
||||
text="VPN Connection Manager",
|
||||
font=("Segoe UI", 20, "bold"),
|
||||
bg=self.bg_color,
|
||||
fg=self.fg_color
|
||||
)
|
||||
title_label.pack(side=tk.LEFT)
|
||||
|
||||
# Search bar with modern styling
|
||||
search_container = tk.Frame(header_frame, bg=self.bg_color)
|
||||
search_container.pack(fill=tk.X, pady=(20, 10))
|
||||
|
||||
search_frame = tk.Frame(search_container, bg=self.entry_bg,
|
||||
highlightbackground=self.accent_color, highlightthickness=1)
|
||||
search_frame.pack(fill=tk.X)
|
||||
|
||||
search_label = tk.Label(
|
||||
search_frame,
|
||||
text="🔍",
|
||||
font=("Segoe UI Emoji", 14),
|
||||
bg=self.entry_bg,
|
||||
fg=self.accent_color
|
||||
)
|
||||
search_label.pack(side=tk.LEFT, padx=(10, 5))
|
||||
|
||||
self.search_var = tk.StringVar()
|
||||
self.search_var.trace("w", self.filter_customers)
|
||||
|
||||
search_entry = tk.Entry(
|
||||
search_frame,
|
||||
textvariable=self.search_var,
|
||||
font=("Segoe UI", 12),
|
||||
bg=self.entry_bg,
|
||||
fg=self.fg_color,
|
||||
insertbackground=self.accent_color,
|
||||
relief=tk.FLAT,
|
||||
bd=0
|
||||
)
|
||||
search_entry.pack(side=tk.LEFT, fill=tk.X,
|
||||
expand=True, padx=(0, 10), pady=10)
|
||||
|
||||
# Main container for two columns
|
||||
self.columns_container = tk.Frame(self.window, bg=self.bg_color)
|
||||
self.columns_container.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
|
||||
|
||||
# Left column - Active customers
|
||||
left_column = tk.Frame(self.columns_container, bg=self.bg_color)
|
||||
left_column.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))
|
||||
|
||||
# Active customers header
|
||||
active_header = tk.Frame(left_column, bg=self.bg_color)
|
||||
active_header.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
active_title = tk.Label(
|
||||
active_header,
|
||||
text="✅ Active Customers",
|
||||
font=("Segoe UI", 14, "bold"),
|
||||
bg=self.bg_color,
|
||||
fg=self.success_color
|
||||
)
|
||||
active_title.pack(side=tk.LEFT)
|
||||
|
||||
# Active customers scrollable area
|
||||
active_canvas = tk.Canvas(
|
||||
left_column, bg=self.bg_color, highlightthickness=0)
|
||||
active_scrollbar = ttk.Scrollbar(
|
||||
left_column, orient="vertical", command=active_canvas.yview)
|
||||
self.active_frame = tk.Frame(active_canvas, bg=self.bg_color)
|
||||
|
||||
self.active_frame.bind(
|
||||
"<Configure>",
|
||||
lambda e: active_canvas.configure(
|
||||
scrollregion=active_canvas.bbox("all"))
|
||||
)
|
||||
|
||||
active_canvas.create_window(
|
||||
(0, 0), window=self.active_frame, anchor="nw")
|
||||
active_canvas.configure(yscrollcommand=active_scrollbar.set)
|
||||
|
||||
active_canvas.pack(side="left", fill="both", expand=True)
|
||||
active_scrollbar.pack(side="right", fill="y")
|
||||
|
||||
# Separator
|
||||
separator = tk.Frame(self.columns_container, bg="#3a3f5c", width=2)
|
||||
separator.pack(side=tk.LEFT, fill=tk.Y, padx=5)
|
||||
|
||||
# Right column - Inactive customers
|
||||
right_column = tk.Frame(self.columns_container, bg=self.bg_color)
|
||||
right_column.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(5, 0))
|
||||
|
||||
# Inactive customers header
|
||||
inactive_header = tk.Frame(right_column, bg=self.bg_color)
|
||||
inactive_header.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
inactive_title = tk.Label(
|
||||
inactive_header,
|
||||
text="💤 Inactive Customers",
|
||||
font=("Segoe UI", 14, "bold"),
|
||||
bg=self.bg_color,
|
||||
fg="#8892b0"
|
||||
)
|
||||
inactive_title.pack(side=tk.LEFT)
|
||||
|
||||
# Inactive customers scrollable area
|
||||
inactive_canvas = tk.Canvas(
|
||||
right_column, bg=self.bg_color, highlightthickness=0)
|
||||
inactive_scrollbar = ttk.Scrollbar(
|
||||
right_column, orient="vertical", command=inactive_canvas.yview)
|
||||
self.inactive_frame = tk.Frame(inactive_canvas, bg=self.bg_color)
|
||||
|
||||
self.inactive_frame.bind(
|
||||
"<Configure>",
|
||||
lambda e: inactive_canvas.configure(
|
||||
scrollregion=inactive_canvas.bbox("all"))
|
||||
)
|
||||
|
||||
inactive_canvas.create_window(
|
||||
(0, 0), window=self.inactive_frame, anchor="nw")
|
||||
inactive_canvas.configure(yscrollcommand=inactive_scrollbar.set)
|
||||
|
||||
inactive_canvas.pack(side="left", fill="both", expand=True)
|
||||
inactive_scrollbar.pack(side="right", fill="y")
|
||||
|
||||
self.active_customer_frames = []
|
||||
self.inactive_customer_frames = []
|
||||
self.render_customers()
|
||||
|
||||
def setup_tray(self):
|
||||
# Create tray icon in a separate thread
|
||||
def run_tray():
|
||||
image = self.create_tray_icon()
|
||||
menu = pystray.Menu(
|
||||
pystray.MenuItem("Open VPN Manager",
|
||||
self.show_window_from_tray, default=True),
|
||||
pystray.MenuItem("Quit", self.quit_app)
|
||||
)
|
||||
# Set both menu and default click action
|
||||
self.icon = pystray.Icon("VPNTray", image, menu=menu)
|
||||
# Add left-click handler
|
||||
|
||||
def on_clicked(icon, item):
|
||||
print("Tray icon clicked!")
|
||||
self.show_window_from_tray()
|
||||
|
||||
self.icon.default_menu_item = menu.items[0]
|
||||
print("Starting system tray icon...")
|
||||
self.icon.run()
|
||||
|
||||
tray_thread = threading.Thread(target=run_tray, daemon=True)
|
||||
tray_thread.start()
|
||||
|
||||
def render_customers(self):
|
||||
# Clear existing customer frames
|
||||
for frame in self.active_customer_frames:
|
||||
frame.destroy()
|
||||
self.active_customer_frames.clear()
|
||||
|
||||
for frame in self.inactive_customer_frames:
|
||||
frame.destroy()
|
||||
self.inactive_customer_frames.clear()
|
||||
|
||||
# Separate customers with active and inactive locations
|
||||
customers_with_active = []
|
||||
customers_with_inactive = []
|
||||
|
||||
for customer in self.filtered_customers:
|
||||
active_locations = customer.get_active_locations()
|
||||
inactive_locations = customer.get_inactive_locations()
|
||||
|
||||
if active_locations:
|
||||
# Customer has at least one active location - show only active locations
|
||||
customer_data = Customer(
|
||||
name=customer.name,
|
||||
locations=active_locations
|
||||
)
|
||||
customers_with_active.append(customer_data)
|
||||
|
||||
if inactive_locations:
|
||||
# Customer has at least one inactive location - show only inactive locations
|
||||
customer_data = Customer(
|
||||
name=customer.name,
|
||||
locations=inactive_locations
|
||||
)
|
||||
customers_with_inactive.append(customer_data)
|
||||
|
||||
# Render customers with active locations on the left
|
||||
for customer in customers_with_active:
|
||||
self.create_customer_with_active_locations(customer)
|
||||
|
||||
# Render customers with inactive locations on the right
|
||||
for customer in customers_with_inactive:
|
||||
self.create_customer_without_active_locations(customer)
|
||||
|
||||
def create_customer_with_active_locations(self, customer):
|
||||
# Customer container card
|
||||
customer_frame = tk.Frame(
|
||||
self.active_frame,
|
||||
bg=self.button_bg,
|
||||
relief=tk.FLAT,
|
||||
highlightbackground="#3a3f5c",
|
||||
highlightthickness=1
|
||||
)
|
||||
customer_frame.pack(fill=tk.X, pady=8, padx=5)
|
||||
self.active_customer_frames.append(customer_frame)
|
||||
|
||||
# Customer header
|
||||
customer_header = tk.Frame(customer_frame, bg=self.button_bg)
|
||||
customer_header.pack(fill=tk.X, padx=15, pady=(10, 5))
|
||||
|
||||
customer_label = tk.Label(
|
||||
customer_header,
|
||||
text=f"🏢 {customer.name}",
|
||||
font=("Segoe UI", 14, "bold"),
|
||||
bg=self.button_bg,
|
||||
fg=self.accent_color
|
||||
)
|
||||
customer_label.pack(anchor="w")
|
||||
|
||||
# Render each active location
|
||||
for location in customer.locations:
|
||||
self.create_active_location_card(
|
||||
location, customer_frame, customer.name)
|
||||
|
||||
def create_active_location_card(self, location, parent_frame, customer_name):
|
||||
# Location card within customer container
|
||||
card_frame = tk.Frame(
|
||||
parent_frame,
|
||||
bg="#2a2e3f",
|
||||
relief=tk.FLAT,
|
||||
highlightbackground="#3a3f5c",
|
||||
highlightthickness=1
|
||||
)
|
||||
card_frame.pack(fill=tk.X, pady=5, padx=(20, 10))
|
||||
|
||||
# Location header
|
||||
header = tk.Frame(card_frame, bg="#2a2e3f")
|
||||
header.pack(fill=tk.X, padx=10, pady=8)
|
||||
|
||||
# Location name and VPN type
|
||||
info_frame = tk.Frame(header, bg="#2a2e3f")
|
||||
info_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||
|
||||
# Location name
|
||||
location_label = tk.Label(
|
||||
info_frame,
|
||||
text=f"📍 {location.name}",
|
||||
font=("Segoe UI", 12, "bold"),
|
||||
bg="#2a2e3f",
|
||||
fg=self.fg_color,
|
||||
anchor="w"
|
||||
)
|
||||
location_label.pack(anchor="w")
|
||||
|
||||
# VPN type with icon
|
||||
vpn_icons = {
|
||||
"OpenVPN": "🔒",
|
||||
"WireGuard": "⚡",
|
||||
"IPSec": "🛡️"
|
||||
}
|
||||
vpn_icon = vpn_icons.get(location.vpn_type, "🔑")
|
||||
|
||||
type_label = tk.Label(
|
||||
info_frame,
|
||||
text=f"{vpn_icon} {location.vpn_type} VPN",
|
||||
font=("Segoe UI", 10),
|
||||
bg="#2a2e3f",
|
||||
fg="#8892b0",
|
||||
anchor="w"
|
||||
)
|
||||
type_label.pack(anchor="w")
|
||||
|
||||
# Connection controls
|
||||
controls_frame = tk.Frame(header, bg="#2a2e3f")
|
||||
controls_frame.pack(side=tk.RIGHT)
|
||||
|
||||
# Status indicator with modern colors
|
||||
status_color = self.success_color if location.connected else self.danger_color
|
||||
status_text = "● Connected" if location.connected else "○ Disconnected"
|
||||
|
||||
status_frame = tk.Frame(controls_frame, bg="#2a2e3f")
|
||||
status_frame.pack(side=tk.LEFT, padx=(0, 10))
|
||||
|
||||
status_label = tk.Label(
|
||||
status_frame,
|
||||
text=status_text,
|
||||
font=("Segoe UI", 10, "bold"),
|
||||
bg="#2a2e3f",
|
||||
fg=status_color
|
||||
)
|
||||
status_label.pack()
|
||||
|
||||
# Connect/Disconnect button with modern styling
|
||||
btn_text = "Disconnect" if location.connected else "Connect"
|
||||
btn_bg = self.danger_color if location.connected else self.accent_color
|
||||
|
||||
connect_btn = tk.Button(
|
||||
controls_frame,
|
||||
text=btn_text,
|
||||
font=("Segoe UI", 10, "bold"),
|
||||
bg=btn_bg,
|
||||
fg="white",
|
||||
activebackground=self.hover_bg,
|
||||
activeforeground="white",
|
||||
relief=tk.FLAT,
|
||||
padx=15,
|
||||
pady=5,
|
||||
cursor="hand2",
|
||||
command=lambda l=location: self.toggle_connection(l)
|
||||
)
|
||||
connect_btn.pack(side=tk.LEFT, padx=3)
|
||||
|
||||
# Route button with modern styling
|
||||
route_btn = tk.Button(
|
||||
controls_frame,
|
||||
text="Routes",
|
||||
font=("Segoe UI", 10),
|
||||
bg=self.hover_bg,
|
||||
fg=self.fg_color,
|
||||
activebackground=self.accent_color,
|
||||
activeforeground="white",
|
||||
relief=tk.FLAT,
|
||||
padx=15,
|
||||
pady=5,
|
||||
cursor="hand2",
|
||||
command=lambda l=location: self.set_route(l)
|
||||
)
|
||||
route_btn.pack(side=tk.LEFT, padx=3)
|
||||
|
||||
# Deactivate button
|
||||
deactivate_btn = tk.Button(
|
||||
controls_frame,
|
||||
text="Deactivate",
|
||||
font=("Segoe UI", 10),
|
||||
bg=self.warning_color,
|
||||
fg="white",
|
||||
activebackground=self.hover_bg,
|
||||
activeforeground="white",
|
||||
relief=tk.FLAT,
|
||||
padx=15,
|
||||
pady=5,
|
||||
cursor="hand2",
|
||||
command=lambda l=location: self.deactivate_location(
|
||||
l, customer_name)
|
||||
)
|
||||
deactivate_btn.pack(side=tk.LEFT, padx=3)
|
||||
|
||||
# Hosts/Services section
|
||||
if location.hosts:
|
||||
hosts_frame = tk.Frame(card_frame, bg="#2a2e3f")
|
||||
hosts_frame.pack(fill=tk.X, padx=20, pady=(0, 10))
|
||||
|
||||
# Services header with separator
|
||||
separator = tk.Frame(hosts_frame, bg="#3a3f5c", height=1)
|
||||
separator.pack(fill=tk.X, pady=(0, 8))
|
||||
|
||||
hosts_label = tk.Label(
|
||||
hosts_frame,
|
||||
text="💼 Available Services",
|
||||
font=("Segoe UI", 10, "bold"),
|
||||
bg="#2a2e3f",
|
||||
fg=self.accent_color,
|
||||
anchor="w"
|
||||
)
|
||||
hosts_label.pack(anchor="w", pady=(0, 8))
|
||||
|
||||
for host in location.hosts:
|
||||
# Modern service item with rounded appearance
|
||||
host_item = tk.Frame(
|
||||
hosts_frame, bg="#1a1d29", highlightbackground="#3a3f5c", highlightthickness=1)
|
||||
host_item.pack(fill=tk.X, pady=3, padx=2)
|
||||
|
||||
# Host type icon with improved icons
|
||||
type_icon = {
|
||||
"SSH": "💻",
|
||||
"Web": "🌐",
|
||||
"SMB": "📂",
|
||||
"PostgreSQL": "🗃️",
|
||||
"Redis": "🗂️"
|
||||
}.get(host.type, "📡")
|
||||
|
||||
# Left side with icon and text
|
||||
info_frame = tk.Frame(host_item, bg="#1a1d29")
|
||||
info_frame.pack(side=tk.LEFT, fill=tk.X,
|
||||
expand=True, padx=10, pady=6)
|
||||
|
||||
icon_label = tk.Label(
|
||||
info_frame,
|
||||
text=type_icon,
|
||||
font=("Segoe UI Emoji", 12),
|
||||
bg="#1a1d29"
|
||||
)
|
||||
icon_label.pack(side=tk.LEFT, padx=(0, 8))
|
||||
|
||||
# Service details
|
||||
details_frame = tk.Frame(info_frame, bg="#1a1d29")
|
||||
details_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||
|
||||
name_label = tk.Label(
|
||||
details_frame,
|
||||
text=host.name,
|
||||
font=("Segoe UI", 9, "bold"),
|
||||
bg="#1a1d29",
|
||||
fg=self.fg_color,
|
||||
anchor="w"
|
||||
)
|
||||
name_label.pack(anchor="w")
|
||||
|
||||
addr_label = tk.Label(
|
||||
details_frame,
|
||||
text=host.address,
|
||||
font=("Consolas", 8),
|
||||
bg="#1a1d29",
|
||||
fg="#8892b0",
|
||||
anchor="w"
|
||||
)
|
||||
addr_label.pack(anchor="w")
|
||||
|
||||
# Quick access button with better styling
|
||||
if host.type in ["SSH", "Web"]:
|
||||
access_btn = tk.Button(
|
||||
host_item,
|
||||
text="Launch",
|
||||
font=("Segoe UI", 9, "bold"),
|
||||
bg=self.accent_color,
|
||||
fg="white",
|
||||
activebackground=self.hover_bg,
|
||||
activeforeground="white",
|
||||
relief=tk.FLAT,
|
||||
padx=12,
|
||||
pady=4,
|
||||
cursor="hand2",
|
||||
command=lambda h=host: self.open_service(h)
|
||||
)
|
||||
access_btn.pack(side=tk.RIGHT, padx=8, pady=4)
|
||||
|
||||
def create_customer_without_active_locations(self, customer):
|
||||
# Customer container card
|
||||
customer_frame = tk.Frame(
|
||||
self.inactive_frame,
|
||||
bg=self.button_bg,
|
||||
relief=tk.FLAT,
|
||||
highlightbackground="#3a3f5c",
|
||||
highlightthickness=1
|
||||
)
|
||||
customer_frame.pack(fill=tk.X, pady=8, padx=5)
|
||||
self.inactive_customer_frames.append(customer_frame)
|
||||
|
||||
# Customer header
|
||||
customer_header = tk.Frame(customer_frame, bg=self.button_bg)
|
||||
customer_header.pack(fill=tk.X, padx=15, pady=(10, 5))
|
||||
|
||||
customer_label = tk.Label(
|
||||
customer_header,
|
||||
text=f"🏢 {customer.name}",
|
||||
font=("Segoe UI", 14, "bold"),
|
||||
bg=self.button_bg,
|
||||
fg="#8892b0"
|
||||
)
|
||||
customer_label.pack(anchor="w")
|
||||
|
||||
# Render all locations for this customer
|
||||
for location in customer.locations:
|
||||
self.create_inactive_location_card(
|
||||
location, customer_frame, customer.name)
|
||||
|
||||
def create_inactive_location_card(self, location, parent_frame, customer_name):
|
||||
# Location card within customer container
|
||||
card_frame = tk.Frame(
|
||||
parent_frame,
|
||||
bg="#2a2e3f",
|
||||
relief=tk.FLAT,
|
||||
highlightbackground="#3a3f5c",
|
||||
highlightthickness=1
|
||||
)
|
||||
card_frame.pack(fill=tk.X, pady=5, padx=(20, 10))
|
||||
|
||||
# Location info container
|
||||
info_container = tk.Frame(card_frame, bg="#2a2e3f")
|
||||
info_container.pack(fill=tk.X, padx=12, pady=10)
|
||||
|
||||
# Location and VPN type
|
||||
info_frame = tk.Frame(info_container, bg="#2a2e3f")
|
||||
info_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||
|
||||
# Location name
|
||||
location_label = tk.Label(
|
||||
info_frame,
|
||||
text=f"📍 {location.name}",
|
||||
font=("Segoe UI", 12, "bold"),
|
||||
bg="#2a2e3f",
|
||||
fg=self.fg_color,
|
||||
anchor="w"
|
||||
)
|
||||
location_label.pack(anchor="w")
|
||||
|
||||
# VPN type with icon
|
||||
vpn_icons = {
|
||||
"OpenVPN": "🔒",
|
||||
"WireGuard": "⚡",
|
||||
"IPSec": "🛡️"
|
||||
}
|
||||
vpn_icon = vpn_icons.get(location.vpn_type, "🔑")
|
||||
|
||||
type_label = tk.Label(
|
||||
info_frame,
|
||||
text=f"{vpn_icon} {location.vpn_type} VPN",
|
||||
font=("Segoe UI", 10),
|
||||
bg="#2a2e3f",
|
||||
fg="#8892b0",
|
||||
anchor="w"
|
||||
)
|
||||
type_label.pack(anchor="w", pady=(2, 0))
|
||||
|
||||
# Service count summary
|
||||
service_count = len(location.hosts)
|
||||
services_label = tk.Label(
|
||||
info_frame,
|
||||
text=f"📊 {service_count} services available",
|
||||
font=("Segoe UI", 9),
|
||||
bg="#2a2e3f",
|
||||
fg="#6c757d",
|
||||
anchor="w"
|
||||
)
|
||||
services_label.pack(anchor="w", pady=(4, 0))
|
||||
|
||||
# Set Active button
|
||||
activate_btn = tk.Button(
|
||||
info_container,
|
||||
text="Set Active",
|
||||
font=("Segoe UI", 10, "bold"),
|
||||
bg=self.accent_color,
|
||||
fg="white",
|
||||
activebackground=self.hover_bg,
|
||||
activeforeground="white",
|
||||
relief=tk.FLAT,
|
||||
padx=20,
|
||||
pady=8,
|
||||
cursor="hand2",
|
||||
command=lambda l=location: self.set_location_active(
|
||||
l, customer_name)
|
||||
)
|
||||
activate_btn.pack(side=tk.RIGHT)
|
||||
|
||||
def set_location_active(self, location, customer_name):
|
||||
# Set this location as active
|
||||
# Find the location in the original data structure and update it
|
||||
for customer in self.customers:
|
||||
if customer.name == customer_name:
|
||||
target_location = customer.get_location_by_name(location.name)
|
||||
if target_location:
|
||||
target_location.active = True
|
||||
print(
|
||||
f"Mock: Setting {customer.name} - {target_location.name} as active")
|
||||
break
|
||||
self.render_customers()
|
||||
|
||||
def deactivate_location(self, location, customer_name):
|
||||
# Deactivate this location
|
||||
for customer in self.customers:
|
||||
if customer.name == customer_name:
|
||||
target_location = customer.get_location_by_name(location.name)
|
||||
if target_location:
|
||||
target_location.active = False
|
||||
print(
|
||||
f"Mock: Deactivating {customer.name} - {target_location.name}")
|
||||
break
|
||||
self.render_customers()
|
||||
|
||||
def filter_customers(self, *args):
|
||||
search_term = self.search_var.get().lower()
|
||||
if search_term:
|
||||
self.filtered_customers = []
|
||||
for customer in self.customers:
|
||||
# Check if search term matches customer name
|
||||
if search_term in customer.name.lower():
|
||||
self.filtered_customers.append(customer)
|
||||
else:
|
||||
# Check if search term matches any location or host
|
||||
matching_locations = []
|
||||
for location in customer.locations:
|
||||
if (search_term in location.name.lower() or
|
||||
search_term in location.vpn_type.lower() or
|
||||
any(search_term in h.name.lower() or
|
||||
search_term in h.address.lower()
|
||||
for h in location.hosts)):
|
||||
matching_locations.append(location)
|
||||
|
||||
if matching_locations:
|
||||
# Create a filtered customer with only matching locations
|
||||
filtered_customer = Customer(
|
||||
name=customer.name,
|
||||
locations=matching_locations
|
||||
)
|
||||
self.filtered_customers.append(filtered_customer)
|
||||
else:
|
||||
self.filtered_customers = self.customers.copy()
|
||||
|
||||
self.render_customers()
|
||||
|
||||
def toggle_connection(self, location):
|
||||
# Mock toggle connection for location
|
||||
location.connected = not location.connected
|
||||
status = "connected to" if location.connected else "disconnected from"
|
||||
print(f"Mock: {status} - {location.name} via {location.vpn_type}")
|
||||
self.render_customers()
|
||||
|
||||
def set_route(self, location):
|
||||
print(f"Mock: Setting route for {location.name}")
|
||||
|
||||
def open_service(self, host):
|
||||
print(
|
||||
f"Mock: Opening {host.type} service: {host.name} at {host.address}")
|
||||
|
||||
def show_window_from_tray(self, icon=None, item=None):
|
||||
# Put command in queue to be processed by main thread
|
||||
print("Adding show_window to queue...")
|
||||
self.command_queue.put("show_window")
|
||||
|
||||
def show_window(self):
|
||||
print("Showing window...")
|
||||
self.window.deiconify()
|
||||
self.window.lift()
|
||||
self.window.focus_force()
|
||||
print("Window should be visible now")
|
||||
|
||||
def hide_window(self):
|
||||
self.window.withdraw()
|
||||
|
||||
def quit_app(self, icon=None, item=None):
|
||||
if self.icon:
|
||||
self.icon.stop()
|
||||
self.window.quit()
|
||||
sys.exit(0)
|
||||
|
||||
def create_tray_icon(self):
|
||||
# Create a simple VPN icon
|
||||
width = 64
|
||||
height = 64
|
||||
image = Image.new('RGBA', (width, height), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
# Draw a shield shape for VPN icon
|
||||
shield_points = [
|
||||
(32, 10), # Top center
|
||||
(50, 20), # Top right
|
||||
(50, 35), # Right side
|
||||
(32, 54), # Bottom point
|
||||
(14, 35), # Left side
|
||||
(14, 20), # Top left
|
||||
(32, 10) # Close shape
|
||||
]
|
||||
draw.polygon(shield_points, fill=(
|
||||
66, 135, 245), outline=(255, 255, 255))
|
||||
|
||||
# Draw a lock symbol
|
||||
draw.ellipse([25, 23, 39, 32], fill=(255, 255, 255))
|
||||
draw.rectangle([27, 28, 37, 38], fill=(255, 255, 255))
|
||||
|
||||
return image
|
||||
|
||||
def process_queue(self):
|
||||
# Process commands from the queue
|
||||
try:
|
||||
while True:
|
||||
command = self.command_queue.get_nowait()
|
||||
print(f"Processing command: {command}")
|
||||
if command == "show_window":
|
||||
self.show_window()
|
||||
except queue.Empty:
|
||||
pass
|
||||
# Schedule next check
|
||||
self.window.after(100, self.process_queue)
|
||||
|
||||
def run(self):
|
||||
# Start processing queue
|
||||
self.process_queue()
|
||||
# Start the tkinter main loop in the main thread
|
||||
self.window.mainloop()
|
||||
|
||||
|
||||
def main():
|
||||
app = VPNManagerWindow()
|
||||
app.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user