754 lines
26 KiB
Python
754 lines
26 KiB
Python
#!/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()
|