208 lines
8.4 KiB
Python
208 lines
8.4 KiB
Python
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"<b>📍 {escaped_location_name}</b>")
|
|
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"<small>{type_text}</small>")
|
|
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"<small>{addresses_text}</small>")
|
|
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"<small>{networks_text}</small>")
|
|
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"<small><span color='{status_color}'>{status_text}</span></small>")
|
|
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("<b>Infrastructure</b>")
|
|
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"<b>📍 {escaped_location_name}</b>")
|
|
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"<small>{details_text}</small>")
|
|
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)
|