from models import VPNType
from .host_item import HostItem
from gi.repository import Gtk
import gi
gi.require_version('Gtk', '3.0')
def escape_markup(text: str) -> str:
"""Escape special characters for Pango markup."""
return text.replace('&', '&').replace('<', '<').replace('>', '>')
class ActiveLocationCard:
def __init__(self, location, customer_name, callbacks):
self.location = location
self.customer_name = customer_name
self.callbacks = callbacks
self.widget = self._create_widget()
def _create_widget(self):
# Clean card layout - just a box with proper spacing
location_vbox = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, spacing=8)
# Location header with controls
header_box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
location_vbox.pack_start(header_box, False, False, 0)
# Location info
info_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
header_box.pack_start(info_vbox, True, True, 0)
# Location name with VPN type
location_label = Gtk.Label()
escaped_location_name = escape_markup(self.location.name)
location_label.set_markup(f"📍 {escaped_location_name}")
location_label.set_halign(Gtk.Align.START)
info_vbox.pack_start(location_label, False, False, 0)
# VPN type and external address
vpn_icons = {
VPNType.OPENVPN: "🔒",
VPNType.WIREGUARD: "⚡",
VPNType.IPSEC: "🛡️"
}
vpn_icon = vpn_icons.get(self.location.vpn_type, "🔑")
type_text = f"{vpn_icon} {self.location.vpn_type.value} VPN"
if self.location.external_addresses:
if len(self.location.external_addresses) == 1:
type_text += f" • 🌐 {self.location.external_addresses[0]}"
else:
type_text += f" • 🌐 {len(self.location.external_addresses)} endpoints"
type_label = Gtk.Label()
type_label.set_markup(f"{type_text}")
type_label.set_halign(Gtk.Align.START)
info_vbox.pack_start(type_label, False, False, 0)
# External addresses and networks if available
if self.location.external_addresses and len(self.location.external_addresses) > 1:
# Show full list if more than one
addresses_text = "🌐 External: " + \
", ".join(self.location.external_addresses)
addresses_label = Gtk.Label()
addresses_label.set_markup(f"{addresses_text}")
addresses_label.set_halign(Gtk.Align.START)
info_vbox.pack_start(addresses_label, False, False, 0)
if self.location.networks:
networks_text = "📡 Networks: " + ", ".join(self.location.networks)
networks_label = Gtk.Label()
networks_label.set_markup(f"{networks_text}")
networks_label.set_halign(Gtk.Align.START)
info_vbox.pack_start(networks_label, False, False, 0)
# Status and controls
controls_box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
header_box.pack_end(controls_box, False, False, 0)
# Status
status_text = "● Connected" if self.location.connected else "○ Disconnected"
status_color = "#4caf50" if self.location.connected else "#999"
status_label = Gtk.Label()
status_label.set_markup(
f"{status_text}")
controls_box.pack_start(status_label, False, False, 0)
# Connect/Disconnect button
btn_text = "Disconnect" if self.location.connected else "Connect"
connect_btn = Gtk.Button(label=btn_text)
if self.location.connected:
connect_btn.get_style_context().add_class("destructive-action")
else:
connect_btn.get_style_context().add_class("suggested-action")
connect_btn.connect("clicked", self._on_connect_clicked)
controls_box.pack_start(connect_btn, False, False, 0)
# X button to deactivate (close button style)
close_btn = Gtk.Button(label="✕")
close_btn.set_tooltip_text("Deactivate location")
close_btn.get_style_context().add_class("circular")
close_btn.connect("clicked", self._on_deactivate_clicked)
controls_box.pack_start(close_btn, False, False, 0)
# Hosts section if available
if self.location.hosts:
hosts_label = Gtk.Label()
hosts_label.set_markup("Infrastructure")
hosts_label.set_halign(Gtk.Align.START)
hosts_label.set_margin_top(8)
location_vbox.pack_start(hosts_label, False, False, 0)
# Hosts box with indent
hosts_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, spacing=8)
hosts_box.set_margin_start(16)
location_vbox.pack_start(hosts_box, False, False, 0)
for host in self.location.hosts:
host_item = HostItem(host, self.location,
self.callbacks['open_service'])
hosts_box.pack_start(host_item.widget, False, False, 0)
return location_vbox
def _on_connect_clicked(self, button):
self.callbacks['toggle_connection'](self.location)
def _on_deactivate_clicked(self, button):
self.callbacks['deactivate_location'](
self.location, self.customer_name)
class InactiveLocationCard:
def __init__(self, location, customer_name, callbacks):
self.location = location
self.customer_name = customer_name
self.callbacks = callbacks
self.widget = self._create_widget()
def _create_widget(self):
# Clean horizontal layout
location_hbox = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
# Location info
info_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
location_hbox.pack_start(info_vbox, True, True, 0)
# Location name
location_label = Gtk.Label()
escaped_location_name = escape_markup(self.location.name)
location_label.set_markup(f"📍 {escaped_location_name}")
location_label.set_halign(Gtk.Align.START)
info_vbox.pack_start(location_label, False, False, 0)
# VPN type, external address and host count
vpn_icons = {
VPNType.OPENVPN: "🔒",
VPNType.WIREGUARD: "⚡",
VPNType.IPSEC: "🛡️"
}
vpn_icon = vpn_icons.get(self.location.vpn_type, "🔑")
host_count = len(self.location.hosts)
details_text = f"{vpn_icon} {self.location.vpn_type.value} VPN • {host_count} hosts"
if self.location.external_addresses:
if len(self.location.external_addresses) == 1:
details_text += f" • 🌐 {self.location.external_addresses[0]}"
else:
details_text += f" • 🌐 {len(self.location.external_addresses)} endpoints"
if self.location.networks:
network_count = len(self.location.networks)
details_text += f" • {network_count} network{'s' if network_count > 1 else ''}"
details_label = Gtk.Label()
details_label.set_markup(f"{details_text}")
details_label.set_halign(Gtk.Align.START)
info_vbox.pack_start(details_label, False, False, 0)
# Button box for multiple buttons
button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
location_hbox.pack_end(button_box, False, False, 0)
# Set as Current button
current_btn = Gtk.Button(label="Set as Current")
current_btn.connect("clicked", self._on_set_current_clicked)
button_box.pack_start(current_btn, False, False, 0)
# Activate button
activate_btn = Gtk.Button(label="Set Active")
activate_btn.get_style_context().add_class("suggested-action")
activate_btn.connect("clicked", self._on_activate_clicked)
button_box.pack_start(activate_btn, False, False, 0)
return location_hbox
def _on_activate_clicked(self, button):
self.callbacks['set_location_active'](
self.location, self.customer_name)
def _on_set_current_clicked(self, button):
self.callbacks['set_current_location'](
self.location, self.customer_name)