From fbacfde9f27cdfa10f9585d740f6cbc1558c7477 Mon Sep 17 00:00:00 2001 From: Alexander Thiess Date: Sun, 7 Sep 2025 23:33:55 +0200 Subject: [PATCH] stuff --- CLAUDE.md | 101 +++-- assets/icons/debian.svg | 1 + assets/icons/docker.svg | 1 + assets/icons/esxi.svg | 1 + assets/icons/exchange.svg | 1 + assets/icons/opnsense.svg | 31 ++ assets/icons/proxmox.svg | 1 + assets/icons/windows-11.png | Bin 0 -> 76463 bytes control-panel.png | Bin 82616 -> 0 bytes current_view.png | Bin 77982 -> 0 bytes customer_card.png | Bin 0 -> 55740 bytes data_loader.py | 155 ++++++- example_customer.yaml | 165 +++++++- init_config.py | 8 +- main.py | 490 ++++++++++++++++++---- models.py | 191 ++++++++- services/__init__.py | 17 +- services/connection_manager.py | 266 ------------ services/passbolt_client.py | 369 ----------------- services/vpn_manager.py | 658 +++++++++++++++++++----------- style.css | 56 +++ utils/__init__.py | 5 + utils/icon_loader.py | 142 +++++++ views/__init__.py | 3 +- views/active_view.py | 37 +- views/inactive_view.py | 36 +- views/log_view.py | 275 +++++++++++++ widgets/__init__.py | 7 +- widgets/active_customer_card.py | 399 ++++++++++++++++++ widgets/customer_card.py | 114 ------ widgets/host_item.py | 69 +++- widgets/inactive_customer_card.py | 118 ++++++ widgets/location_card.py | 145 ++++--- 33 files changed, 2626 insertions(+), 1236 deletions(-) create mode 100644 assets/icons/debian.svg create mode 100644 assets/icons/docker.svg create mode 100644 assets/icons/esxi.svg create mode 100644 assets/icons/exchange.svg create mode 100644 assets/icons/opnsense.svg create mode 100644 assets/icons/proxmox.svg create mode 100644 assets/icons/windows-11.png delete mode 100644 control-panel.png delete mode 100644 current_view.png create mode 100644 customer_card.png delete mode 100644 services/connection_manager.py delete mode 100644 services/passbolt_client.py create mode 100644 style.css create mode 100644 utils/__init__.py create mode 100644 utils/icon_loader.py create mode 100644 views/log_view.py create mode 100644 widgets/active_customer_card.py delete mode 100644 widgets/customer_card.py create mode 100644 widgets/inactive_customer_card.py diff --git a/CLAUDE.md b/CLAUDE.md index 25ce966..906037f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,19 +32,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co **main.py** - Main GUI application entry point - `VPNManagerWindow` class: Primary PyGObject/GTK3-based GUI application -- Implements single-view layout with Gtk.Stack for smooth transitions +- Two-column layout: active customers (left) vs inactive customers (right) - Features system tray integration using `pystray` -- Uses GNOME-style theming with CSS styling for cards -- Includes advanced search functionality with wildcard support (`*`) +- Uses GNOME-style theming with CSS card styling +- Includes comprehensive logging system with collapsible log view - HeaderBar for native GNOME look and feel -- Current location tracking and display +- Current location tracking and enhanced display with network topology **models.py** - Type-safe data model definitions using dataclasses and enums - `ServiceType`: Enum for service types (SSH, Web GUI, RDP, VNC, SMB, Database, FTP) - `HostType`: Enum for host types (Linux, Windows, Windows Server, Proxmox, ESXi, Router, Switch) - `VPNType`: Enum for VPN types (OpenVPN, WireGuard, IPSec) - `Service`: Individual services on hosts with type-safe enums and port numbers -- `Host`: Physical/virtual machines with services and recursive sub-hosts (VMs) +- `Host`: Physical/virtual machines with multiple IP addresses, services, and recursive sub-hosts (VMs) - `Location`: Customer locations with VPN configurations and host infrastructure - `CustomerService`: Customer's cloud/web services (O365, CRM, etc.) - `Customer`: Top-level entities containing services and locations @@ -66,8 +66,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co **widgets/** - Modular UI components using PyGObject - `customer_card.py`: `ActiveCustomerCard` and `InactiveCustomerCard` classes - - Active cards: Interactive buttons for customer services and full location details - - Inactive cards: Read-only service lists and location activation buttons + - **Compact tree-like design**: Hierarchical layout with expand/collapse arrows + - **Card styling**: Customer cards contain location subcards with proper visual hierarchy + - **Multi-column layout**: Fixed-width columns for proper alignment (name, IP, actions) + - **Service action icons**: Direct access buttons for SSH, RDP, Web GUI with tooltips + - **Multiple IP support**: Display primary IP with hover tooltip showing all addresses + - Active cards: Full interaction with connection controls and infrastructure details + - Inactive cards: Activation buttons and current location setting - `location_card.py`: `ActiveLocationCard` and `InactiveLocationCard` classes - Active cards: Connection controls, deactivation (X button), and infrastructure details - Inactive cards: Current location setting and activation buttons @@ -76,6 +81,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - Service buttons for direct access to SSH, Web GUI, RDP services - `__init__.py`: Widget exports for clean imports +**services/** - VPN and credential management (modular architecture) +- `vpn_manager.py`: NetworkManager (nmcli) integration with .ovpn file support +- `passbolt_client.py`: Passbolt CLI client for secure credential management +- `connection_manager.py`: High-level orchestrator combining VPN and credentials +- Support for flexible credential storage (direct username/password or Passbolt UUIDs) + +**views/** - Comprehensive logging system +- `log_view.py`: `LogView` class with collapsible interface + - **Command logging**: Real-time capture of nmcli and system command output + - **Color-coded levels**: Info, success, warning, error with visual distinction + - **Auto-scroll**: Automatic scrolling to latest entries with manual override + - **Expandable/collapsible**: Bottom panel that can be hidden to save space + **Configuration Files** - `init_config.py`: Helper script to initialize user configuration with examples - `example_customer.yaml`: Complete example showing YAML schema with all features @@ -93,13 +111,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - **Active/Inactive**: Locations can be activated for VPN management - **Current Location**: User's physical location (separate from VPN connections) - **Connection State**: VPN connection status independent of location activation +- **Network Topology**: Each location includes internal networks and external endpoints +- **Credential Management**: Flexible credential storage (direct or Passbolt UUID) - Automatic UI updates based on state changes with immediate feedback -**Single-View UI Architecture with Stack Navigation**: -- Uses `Gtk.Stack` for smooth view transitions with crossfade animation -- **Normal mode**: Shows only active locations (full detail view) -- **Search mode**: Shows only inactive locations (activation and current location setting) -- Clean visual separation with no overlapping or confusing dual-column layouts +**Two-Column Layout Architecture**: +- **Left column**: Active customers with full location details and infrastructure +- **Right column**: Inactive customers available for activation +- **Compact design**: Tree-like hierarchy with proper indentation and alignment +- **Real-time filtering**: Search affects both columns simultaneously +- **Dynamic reorganization**: Customers move between columns based on location state **Widget-Based Component System**: - Modular widget classes handle their own GTK widget creation and event handling @@ -118,7 +139,15 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co The application tracks two distinct location concepts: - **Current Location**: Where the user physically is (set via "Set as Current" button) - **Active Locations**: Locations available for VPN connections -- Current location is displayed prominently above the search bar + +**Enhanced Current Location Display**: +- **Prominent info box** with customer name, location, and VPN type +- **Host count summary** with VM breakdown (e.g., "3 hosts (7 total with VMs)") +- **Collapsible infrastructure section** with detailed host and VM information +- **Network topology display**: Internal networks and external endpoints +- **Visual host type icons** (🐧 Linux, 🪟 Windows, 📦 Proxmox, 🌐 Router, etc.) +- **Hierarchical VM display** with service counts and multiple IP addresses +- **Multi-interface support**: Hosts can have multiple IP addresses (firewalls, routers) - Users can set current location from inactive location cards without activating VPN ### Search and Discovery Features @@ -142,16 +171,24 @@ The application tracks two distinct location concepts: ### UI Layout Structure -**Modern Single-View Design**: -- HeaderBar with title and subtitle (GNOME HIG compliance) -- Current location display (centered, prominent) -- Search entry with comprehensive placeholder text (supports `*` wildcard) -- Single-view layout using Gtk.Stack for smooth transitions -- **Normal mode**: Active locations with full interaction (connections, services, infrastructure) -- **Search mode**: Inactive locations with activation and current location setting +**Modern Two-Column Design**: +- HeaderBar with title and current location display +- **Enhanced current location info box** with network topology and collapsible infrastructure +- Search entry with real-time filtering across both columns +- **Left column**: Active customers with full interaction (connections, services, infrastructure) +- **Right column**: Inactive customers with activation and current location setting +- **Compact tree-like cards** with customer cards containing location subcards +- **Fixed-width columns**: Proper alignment of host names, IP addresses, and action icons +- **Collapsible log view**: Bottom panel for command output and system logs - GNOME-style cards with CSS theming, proper spacing, and visual hierarchy - System tray integration for minimize-to-tray behavior +**Customer Card Features**: +- **Active cards**: Start expanded, show full location details and services +- **Inactive cards**: Start collapsed to save space during search +- **Location count badges**: Show number of locations in parentheses +- **Smooth expand/collapse**: Click arrow buttons to toggle content visibility + ### GTK3/PyGObject Specific Features - **CSS styling**: GNOME-style cards with borders, shadows, and adaptive theming @@ -188,12 +225,28 @@ locations: - name: Location Name vpn_type: OpenVPN|WireGuard|IPSec vpn_config: /path/to/config/file - active: true|false # Available for VPN management - connected: true|false # Current VPN connection status + + # VPN credentials (three options): + # Option 1: Dictionary with username/password + vpn_credentials: + username: vpnuser + password: password123 + + # Option 2: Passbolt UUID (for future implementation) + # vpn_credentials: "550e8400-e29b-41d4-a716-446655440000" + + # Option 3: Omit or set to null if no credentials needed + # vpn_credentials: null + + # Note: active and connected are runtime state (not stored in config) + + # Network topology information + external_addresses: [vpn.domain.com, backup.domain.com] # VPN endpoints + networks: [192.168.1.0/24, 10.0.1.0/24] # Internal networks hosts: - name: Host Name - ip_address: IP Address + ip_addresses: [192.168.1.10, 10.0.1.10] # Multiple interfaces supported host_type: Linux|Windows|Windows Server|Proxmox|ESXi|Router|Switch description: Optional description @@ -204,7 +257,7 @@ locations: sub_hosts: # Optional VMs/containers (recursive structure) - name: VM Name - ip_address: VM IP + ip_addresses: [192.168.1.20] # VMs can also have multiple IPs host_type: Linux|Windows|Windows Server services: # Same structure as parent host - name: Service Name diff --git a/assets/icons/debian.svg b/assets/icons/debian.svg new file mode 100644 index 0000000..9460799 --- /dev/null +++ b/assets/icons/debian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/docker.svg b/assets/icons/docker.svg new file mode 100644 index 0000000..0903acd --- /dev/null +++ b/assets/icons/docker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/esxi.svg b/assets/icons/esxi.svg new file mode 100644 index 0000000..f0770ab --- /dev/null +++ b/assets/icons/esxi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/exchange.svg b/assets/icons/exchange.svg new file mode 100644 index 0000000..fba7bfa --- /dev/null +++ b/assets/icons/exchange.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/opnsense.svg b/assets/icons/opnsense.svg new file mode 100644 index 0000000..65e6a58 --- /dev/null +++ b/assets/icons/opnsense.svg @@ -0,0 +1,31 @@ + + + + + diff --git a/assets/icons/proxmox.svg b/assets/icons/proxmox.svg new file mode 100644 index 0000000..fa1d942 --- /dev/null +++ b/assets/icons/proxmox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/windows-11.png b/assets/icons/windows-11.png new file mode 100644 index 0000000000000000000000000000000000000000..ce7b366d9f65d24ae8c313c42df3e84bedc22221 GIT binary patch literal 76463 zcmV)7K*zs{P)u>I{S6+|zd;Gk-MoA-$`QSA8xKoSKTQ8g;tNIJ zT#wg}+taBkP(Xcw@pBWk9K zOoASvnkQmrBd7s-B@F6-3OjIU)!<{&CEwb->YH)93cYt%YL#ajBwZ=FdmyC;;&YLh|dM^ zIDoBwcMW_QaAOMTiGl8Z)vO)xU4E;Q`_;TseY{t zUjg`d>if5w+WLd-+uI*LzH{Gy8=8n8`8Mnu3~+uI?8XpqIf>J|4u?TBx|*4s4Qoxi zzeEsoXds10FHK9nh z#4UgcawWA8lu3?hJCtlk%CQs*P00$xp8&4-=$-=~tpX8W4B!m_TP64&m#j>Obir8O=Mp4|JSwsSylGU)aNH{TfMxwt=~KKN;{8{jMPuFHN>V2RL04lTpnx^`C>h;3r&DE3p_ip;= zgWC@b){TfC{VweP|3AAc1RM*&F77&RodjYVRe()qIyFXP#4D2QZznZZ6Bspi1lOG4 z?HqutOOhyD*|*&@zm33J;aJdiwN6(<{bQ4o)}JQpFR7SV!1LT73b#y6;Ho~*cn zrq^Z6ERB!bWs?(QKbn~y|Kh#ww)WKDJZQQ*G9Z50LfbCCqIy)PonG4QrpLLbS$E4> za_6gN7F$Z1R?8T0I#_4Lys%?d!$S}q^H4StA#ejb1+ZWsW(;-0^$)dVv$t>*JCh%Loy2?*5)xQ(xvH` zj?vs9wp+?^%98_7E9@^0HiH3=m9Zi4UKx8lIB2^p`$JIF6$2qp4f*XL8Sng)IYwP% z2{x9}#Q4~G)05Tb=Vrz~fAJw(u3vE=e(|NfwXMZ;OuKC_?e)?QHID(5M$^2D$p(Aa4_U*|k5JP%!(c5){Rxqhbw5ad>i1A|%5EnMt0 z9oSj5!2sQ)eLLi^tyB>NKp&Ab-4rh^a>d3#q31yJ^DCE$23j!4tAonY{9~R$zw39% zdzvSsn4&UU408ItUA8>%luTygyi(Thr}e*R24Z~yqBCM%-_Umm%1pn7U}e^xoE-bv zE_VF(tB&0EpA`e*7hKT0d%K<9)alrB+|$&)i-O>6f}tZ`*#V3~UW5!IE`NtEvf=`O z8Qf)%b9`xOd?5>D&>{e3XRlzuBDtBrU1uhfe-g}OUjIPLbMfyaO#@R;RyS<1(>nt> zyYyVi)t5LpDj)_;Q2mcfAdbp#2G&wgU53B#9tnX}XZYIO8`cF`mToZknUqEoLD2hG zk(AM_TyW~HY55)jE28fUNrSe)gP{eytMdn7VHurYYO_7;`23lv@waZ;F!S}(pSiB{ ze+A;_U(ma6yOloJ>DuGsV8dS~%TW!v4PgbR=89N?EZ`B?nPBMQc|o16H#&pO^C-+s z0Rv+n6#oFgC>WwLC55rInBtnm?O2io%p1zOlfAbGl0pmaQ{H*dd`_DGW2;s6^03Z7Ww(= z^I*aR_*7a<^|_2!cH{JP^@eq;rapV#3pOoDKrDdpgIX>7P`8sFpfXsv-P*l%S5iV! zE*E$!vth-#?NFs0OVb%tV)>K;x;!3$mkmXb+i}567U6q71_5b4Cp+CeUtX+Mz%h7{ zP{N{KN*0yQxrcNreh#C|5$AbvWYBEM%iQ}GXO(MBGsqZQE2l{gKpY{tAfM`B4zhIV zW-(5Y#B+3rof0#II%EgzXEj8SVWn$;#kVTiyLi6-t39e?X3Kyu2VpMNnB6`zUA<@B zs_FNg_ri_yLjm#g&+px@)wa)-Aj}t?k~K5Ts2+^nYof#roqM!vlV#~VN}0F^ zi%n4e7(0h`kn3!i56R{p{oWT~eCNzm^``9`X5WA6bJlkmh~u2_U0W^tM5mJ;uU&KL zik)7l54b_}?oEo>ABVY8SkiJtuOVE9NsT?lHmc_=f8;SNbOCPG!BEhtI3a z-Qsxo5x@_ax+~>g3NXg2p8(j+ay_`f$|SJZW#GE%;JJyDuR41Bk7Xc!pD) zLXMdG_eq8*-Kdp+qb@fLunh3jezO_BLCK)w3bof_m$Q|VrV)r^P%i#iAaBNhfIw^w z@1rHER1i{K_{}@5IpUm-Gw?*n;KAMrt@X91B3GS~tA0F?rrD{k7$&X!P*I!+ zXX^t4F?4~;)2HeTGt2L1t$soPVtiqyC#!!PG&^y~6-REnL;~XHo!@&@YbkxX+qIo$ zwVL{Jg_mz2J3qKv7GD&Do$0JrcE`)jM}z@FqYb_Hu6H?uy6an#jerq@%mwmv?cy?sN%km^NSSMwH;I8n z1!1r+yI6Wf22al|J6$RbQGi3wUvp1a<5ZV!kb?IcO>p_&ga$@N-!q(3H)=UwuH)`Y z#ZGbAh6m@Ae$N0uGhKaj^ZL2hoc+A@^C1u)a!GH)o%3mzIm@2Wn8z__V!-7EC6K#N z#sz_r08EyGC398GREwpD(9pw^mt(VOCCK+$vVvWf+s<*FW=+)Nd&f*Xky(quPM4tZ zeDJ%gy{3hon#4s`W)|2qo`INs0(@^r1;lDZG3>uvf+!ym+ggH*3mWziMlGdPr=yH& z`P0Sw!7&bgV>z{WcLWe_cILR$+7jf7+VA0W$bXK{pL%igwsdlFcm7rZ;axpUO)=q^ z$?Oa546Ix+Ac~P78dOL zosK^||4}0ZN9?zde$)k()pByYagJ0fq&+LJ?lT|Hxl(?~*h#K$o9Kdaq{`xa9G7HR z1YrhDa8f(0fb}LF0R!iYgx-Elj=(g?)QIk$IMyJes1^?Vjcb1X$O zEar=poRY$Z{+Pq{=Eb|_nC9*3*fGs@`;9Vah`z~d`8NrO1(xfo+08w!zjZ?ksAASa zr@b2*nVf-@#tNYA*l?a1v0m!rN{1tixxNpGMrxR}m=jdWLytywH(lXNNotS(_P zunR^r$SVR9ToKTV%W?9KOQ5#vb3(^4Be2>GLjhuQ=MvVCU09PB0MvonNc4{=YKiuI zEm@kL8vEtygC-B%#fooQ_AMN8ad-Wl3-;ws+nz#QY#4l5;xk2#207WHrWsM2IFf2Z z_Ud9EY&Li4Z82lB7zI3+^0m6$tg~T~borcltXdJ_%!pa-VCJj=F)t7pT=hj6cnk8s z;ybnSK+uZg8Y3_cFuEHjefJ4%7rQh?5I z?IC^0%^sYkbKbvs_$C_M4NmtNQ48aN-!HDmuEy-PUAf|;uQ_hVo?NkIr*!wxON;iM zUN5cZ)iD`3AXlW9E5T2CVAahy{IA>+ZqSYPd6g)zDz2l>bJKyCYk$FY&0D&mYzV9p z%fTEZ-5l`4y5!gijl*@xTg>gd$1)A73V56m)DeIC+Py$*HdiceV)L}lfZ^VggzdVK zM4y)mbS$q)sV5Wa>tQfYNd#kPdXrBB*JhxA3neLnL)dlKx%aK+7S1QnK^H6B%ldWc z+VymUSxVDWNTXXJ4=dv)Wr{?_(VdW}?@&oDJy-LP?hlz}vvvgKSp z9Z*`A>zg&@h94+7Y^CNL0O$Y!u-K&9E)9rbZ&MUVhZJvuw{E z!xwkg%+K2oJMHwyWB|6H13}F{0G?*1@JR9ww?A|~uaIZu(i8?`HgW0(cL6VDI2Q;mwmw}j%9WiY+*eHs~@MbNI5!)YA^0LLHS#ZLoQn~bkjqs58{gfU=pzCY*=+Gb0vcH`3^1Y z=x5N(gfW+0%6f}S#ubO1#nkuA$~k3udU02Sk0Y-=ZpXK6-^LCPTv|%s>vq!iriujs zXIYZs|DaST?BD_HB?_E|><7U|-UgAb8Q3Gw!MFj4R*uB=wWe>@;IOiAN#ZsH(fqrf z&vH+Syp61yzi{xS?P-bL_ml;|( zOZ`|ilE7sz;JD}aA{ZfpBIMh>fHqLV*yN-GvCxqWF5PGcJk=ys8h!zJ;V^&)=DAcC zODtm`#vbBkmJXU3|Il5x&A!6+xTVkO9o}lC&-Z$1e9r*bw}DO-H|JJS8=TV8GiTPp z^CgfF`m*aR8XVY~)GgHoyLOG4{VM8{0P+Ap1;qA!PF5P(^crF^V9~u48n4s>lM#Dn zaCHTU84p%in%-^J^6Z)x*r&iLS-lZFRhhlPsNvnxLef`MX_tXqrov_(S87PcA~7e6 z6#!Q1Wuy=W;9=gNREWTnS*10qPbTei;X}2uE$B4^-Hp4Nq&1ZcV|!OMXc|j6b#RZnVF$Jlz5ooRm`d>F*7sk zsV9fIO>u7SU)D~m<^Qx;C;j}cpT}c%4FxN zT6T6Shey(=3f8wX;=4-IHb?CEmf1k`fA=JylxlZr3Ce#XlLs-9lRqFQQw_kCyh=0g z56?f_tCT$}LZSIu+@ZO@Wxp8t=kN34IeeRO&dbs{F^}}7O$pOW z&wTBik+U+K1M%?=85{Mloc6b4E;3n3>@NgKHpN$|Gq| zu1?0@*3CU)7xc*bzao4^b_clYIAZWN+(+sD(B?Y~0FErF;kOMk_Yi~;Bm24Qf^~r) z$80??cv=G47@=P0TtI{0Jx&~?$%}dQMT|*5{-Zzg#~wxye{cOw|JBd`f(J?KcRl*U zf9|=1vjq9+dKQR_GDZMUoYms*r#+6BLxRtm1~#{wv?S}?1Z-)3Mj5mdxl20^=vw1< z0Q1Hg_zNQ`jvpv_5Uju(8rBx~>-(X9Wt9{F?%gAv3wm%yd;oFmo&*?IP7JN{#p}D< zj3)R)4?r09#lhyh3g9tB;CXM<=Z9DDPMrE8gUXlGOSEhtDtJ#pR6W(Y#+FvW zDA=Vdvc_7|_}L%%ky=0bV?Xlu*Wd7;|NM6zApCu4yVq(g`SnXiQZ`O0o~~w-=Z2Sy7AELF?hqao>uSQOgsAkpq>UTwXc8HnY*4DvdKdkRlVM9>0v3>A#p!0NGdS>jUVr6NqRa+jmSAUjnG z{v1VfAc)64fGKx#BGxxO#^@qh#d+@T_AD#Vd+hQ*kmp3O3|YpO83@$ z>dS`JhqyTiLljugN=AY-Z8hL5U`nSMy!vdo#2|$tHJ+(+f&mZyyu9w8rB;QlqafhU zdhU53V|4X!Kw+WdRm6UCrg2vik2c{36#SW+|Tw!U!LCQe!}S( zmWJ$WU_zjDWGyFlu-3bnkQKWdQI?)-N84ezgpGtexW~D;3jlH6qIi?iw6xw;UAN^t zmkei#ZPW3YLNDt%Ny|O|zLJ~K7c0Yp9{E6sf)fY;@3J)kCrv9+D`GGv6cD{%Ayemv zV(u7~YD}~L^_-I+{@9Ov`uNU>Ypw6ETJMh`e9vY#IRhlTYMW6X{U3?TPJS46F$#^= zv6Keu5V$GJCEgW)a6##k0|WkDW&p-OOvElfs6WIAc6p{a@hrxXDBjl>fZqLikgzu~ zvCt1kZg1M-OLdd7*6-Y=4{N=%rJIHT%n$EE=%w~~$5pIbKsX@y6q^A6LDaLgBBwBv z(&daJE(62vb@BXivPP&lkZ^ROlX~8P$f8Aqm;s*xvG9DRp2XRpKgohd?gWUr-%H{NS7TAF ztU~|ZO2F=P($U-(*t5;t1t};`Q}ED%g+kEgo^%Yv`ye;7rnmbNbE5d|lUTyYQK2a7 z0foDtqjRd+^gnt;iY;p@D1$#2DJ^e;^P%t`FXFlKr92+3MKD>$v>D&Nbp_|zbo{tcQphF9raXrUwX!B z#z=?XknRKG-)Vv#kUw6o&-RmxBQ)mz{HZw*=foM_hlxG@?C-znc{cWZb{PH+09H@4 zl!qfs>P=ZH8lBoOJSZ~=(UQ!2Ls}IpAYQS zP8I}la`r??gJu!-`M?|WsnH|$p=BTrf+a|c1le;^2K9c;;yy0-rOD@0AT_$gOC$9y zkrp)*!~8jF{s7EtR{TPVP)vB5CJm30eb57l|En8-_&dMTS=0F(ux}2|U6~}}@Xj+} z02X=+=UE^9{%<2``JXuh>y;t7EX_m5)b9o{Yq|b5J9Fi3So3@9NdqTN{x)h$|C z;VR-F7!cP3i1j{r7za-bCX9SEd%%@TpAks%?6GWA!SWd#Y4Hrx-PA+&Hk=2tDV*`m zNm>jNc#Qm<98d_aB`Jji5ck0CBXGhGJNy(gsyb$?6oqLP6e9Nmepau01Mz>fGvc~gSu3T`^B2h%mqss@ykL3QUHpKsAVEqrLN4;6}q+)CE^e;zddhE#?(1`Eo5jo2~LW9EloE{SPxQ9(9r)gF@<&gCT0@EGukEg{*|KUxnZ z3xM_eZ$Rb^0;AZ(g9h+*^oX@H;+hC)WrOH54u)3QjfB2N0alIrDv%tW-y{r1=bT;0T3lGt>Sf8#Zh?S-ut{@%P2T*$+tWsX3j$vq|UV3p=ZjU z=GokQRH}8}sDWQ`%8aF4iDy;0gw0yj4&0ds@wGrGdk_;a_8An5fdB|6ta^!$;~4_= z?BQY0dc??vzppg?J9cThc8{3)TD1<*1+FFY^F7xt2EgV&A~3ShLt@Y|8#zKIDIbRj zjK?u^;u!Sva+^r7vZCsmXXjaF4nP=_a-I8A{9F$L6lGxN8S!8J=n>a3zO=IdBtyGO zO7SBxuvdYXW|}$jOBI6vL9II?RRIVRdqThl70n{s%p>qq^0GAe2dh;I@KK@O_t<}| zv$3UoCK!6jc5GlwLw^`&h>HR=9AW#g#u;(YBbL6WRquW<`A}9%EH7o`1T5b2-N&Ug zu>uvWC*yHI)>+9Y2c2}_y=zv3VkS2Lm|j^5E+S#V_@3F}^7$wu9XM(2DhF9{ieo;) zo773H>L6?B1&IBob)b9(VFE4M_~~+S*J9Ny4tzM$BfW5zvE&H=XIT4ZL-K~cXa)e8 z8qC4r94|kt*K6XkGCU_@-vtYw(0Pp1e zAj-Z%vKas|Ubikf;R-<*)>8@@%+P1P>ewwkS2DK)=ly#LU&yh*i)c<8S_?M=vGyGC zrx#$Ms*-sq)s0y&yzC29cMMzLoB@ZW6%T@BAiM@J6o8G&EcWvUwKHqIz>k06_gg{G z+{GXoxJ@DXB(8e6Ota=D5776lT)VrD(zNdLBL=4w?T2TBuWsX#~{fFwIlRN*R@_oWgmj|KA=JtIU}k^ov_!0ZhlW&p2i3&twG z$EfO648%+}E1a|_XqT~>$7HVhx$nDby#Vna-+~?v#P|ime$1LY%NZ&``6R3Ni=nPL zK8_Os=z}Q)d@eQD4}cgLn|7SJn^7j@vA91B;Lc@YUylT2Q^7dXARB&`{SMiBS7uNL z1lQ;Oux83ygN&H-;BiWue+*hy^ggi{H~|P$WXngEKy%qoGdCjOEIrJ;nrq;RiMbdD zf3EBB)gqtrbMHC|GAsv)4$@Me8H>6FTHc=FdlzV$&Vc*@#Q)&~h-*t+h8q{n3w*^s z?{#XFOy^I$_-2}Sa^*g`x%oe2(t=2_mcsdE$Y8;sCX|k3QWuk}0yOLYoR0>{%42k1 zDIt|8>FYyA+-KW`E?9onbs}Jdh)|9y}Sp^f6%oex{pxVdZA5(JzlUqOdZ8IBZjAn z;^!hk4s*5ehd)cXJPK%6@P@xv>%tAt*1kvkKB zAMZ={i3RPgmg$X5XK-qfH@dX-v_Wm{*e4%bIx=ECfcS5{7l@w*S+r^zm!Sb1_`&rd zV2M6WS+m>lZ6gvRK}BU$1e{{P;~8go4o6N&1Co}m!K#sp^~_Z4RXb4i@+=hJDJEJ2 zB1-(-as;ZdE#evRU-ig{YXfmJw*)6G)`fODJ!b4V)my<;W(FSv@ecUmpfXi6B4V29 zeSh{#FMH)C`%Z?VV=*8CvwANSA3kX*@*n2RS>QvfoT$aJp@HsqOajcvTKA7HZ6Fo{ zF>l-)zDHFHSvR>X=0z3El&At}&a91>%SMJ?>J`s8+xvz4b@O><_!ImwH_%r=f0r(} zzx^h;><7Q}Z@-qM4aBF`wCZE;_CoaTR0`VvX=Q-&pUhV+Z&?R`S+04X4@eMQkJ&7( zQ(fo1Ddzy?F~pAqa?bFJZ7$?B8Bgx@RV_-9;@PBkGJOm<7!<$o>K%Q5$8Fr&v+h zWy&_%QUcn2HAYw3;co=Q-H)+~I8q4!=cowJ=C#g%3XlfQB)J-onIe4#Z9v+iZ6yMFJ2Xsnn5VM;MaNnuoAvmZX8u;&w&FF7kqd&&U9@HZF+E5rbKX zf0;=`Eo4nMnYp;kTo=0Km*4feiL3=ctaE(n#)#YQ3uH8Kl6f{xx&(E6-$Wv%cdT7t z$ov7L9}wuN9jsO%SOk%NeSJwwEw0D|^_MSZUYh3n!wKD-z0IG!0f^@tl~w>jvr!&6 zOdkuY9cL1jn|N+mkrpZdCrDHl10p{+Tuw(VY6g0RGswlJ3&WBx2PvLGKpETF?0imE zfRlLb5E0y7n*Qy__>xsn@7UnsgvK)rT?f-4yr1YUf+q*G_;-^7@pJ{l=XN#)HlW)*-|Fdkd7zjyt>4Fn7%(`&a)xj7~JI*s7k~$1`K6x1%g2jY&p5-X&|m|fcQVy%33=kZu9xN&cgd~<5nAkeOqWI zYf$ZyeC95fp=7cYTcCo?ALwNR2u7@I%}dN&X7)pvCM(PuL%=WsOJG1tR|GQHWHJt& z+mJ5qXJrg~o-M{w_J`6#Yx-~fwg55od8=6P2I13c@}pXLPL{0W_c+ghZqSrQ>1A`q z60{3NZ~qzu3E*`O2K=2qLUHRYuQ13_BnNQ1N7YL$@G<$^=+IVPn*J;QgO3?;)p~zc zFD|2{O2VI_*q2#-{(^5*a}?3TwKRTc`_i%Cq5%Aok~Rk(eO)_|rvxy>^N+J3)DJjrCGZg?;_!&UM-Z@h@ZKDKxUSaJC~3(#Q}*w)_c3!s(i zOAEoAvs{}2gsc_T2tPBsUO%yC?K2>@`;C=}0c&~=#PD1)Novrj9AsswB(?p)&s6>V zIjk<#-K8%&2Xy)Ry~UB1Z|6n?urRnwFd|e%HmJ|aNakg9k;+)ymz-Eb4mH?o?Tk1e zYZdd<7`A;SUbps}f!Q->>rz%WP+;#Q`A`G&V*8Q{m6VLzqictyJbOl8Uob>S=l-GM zVD~;24#{4shDXI!)-Lw*a=&KT zIlL%g6aM%ai1Ye%T`kZ+7l?eh9U^?6kPX9U_+>g5;Gky~%DVtatTQ_*V@p0-n3YwF zuPHAjUsNC11cN<=WuL+C4%Vr>EX~eLy$>pUtM>u%Z+if7)#n+p60_aP(F(!0%c6(+ zKMWw&(vtW7z9KUR#>H@i=D;A#prSP~h|K%? z;?IjloodxbApT>$A2AfEb6_^Axqn2F1m+;KRa(`sLDT9djd=*4iB7ho#lF^qq0h2R zq!_`uY@F#JXI6{YFXn7mqznY5V{w)r1W-*NC{X@Z)NVM}*WeCFp59rS{%yN7U9H4r z1HyHK*I_u$0|^N2p`&+JLG^Hnrh4GzyK1Va1mYhkmcv0!-s&3wTL;9LsRJs`%=V!? zlaFx{VXPuaO&})nwLw+k0mT3QAN!aQSG`B@m3QYISql0R$71cp8MaaIN^+GMc2mM? z8G*mrPH{f2#GvYXbAX`>a#BHWTZIB40ES>aXgzxX?F5K7OS|_z1^dCsRZCh*n4m+)X^AnV|(&L(to z8XySC*9~JvpgN8Zp_Erg$l5~wFu_Y2epF=YP!@p2z%9%lqU;uq$NNJN(xr{kbp|A6 zR_9Jm4dYQPjoBXqO4)2af&J}Wn*J65ab$@KBLsX%#wj9W`o2xhR%)`iIS7NO&@)O^ z9GulD+(E%MWy_vC4fVS1vzN+M#g$q#lh1qBEPdE_J&B}M!;-guwqg+G3qFB(t@k~r zs&#wrC+C%m*y+_}tepJ@ee#g090UOp3yibi2k06%z+SnfUf!eT{xlNKt~2-GyanKz z^?TLaysR$`vYS4Op0cH3RE?aTtV;Po47 zq>ea!zmUU6-z37tUeiwjF9_&^_YnY8DwSB#4_Tnynv1XGV(FZXdJEZDmg1YQ zt6nme%yO3{))-(lEnE+QqOLPi?{Q#sW4lmY`&=E{8s|=z66O zuvio3GEWkZH05qfIT(xaqf~nVJ0o5@BQB(2;xUXSc9B1$@Fu& zMppCuwJitke14Zo>KzCZVL`P7^T9ki8Ol z2zHz==&q~{v}|$q01}DKwdx``@pE3j<;y;k^mAoe-?k(ooY*E%MPYlyYC(*%>ik$- z4hJ9>SsP+z5AO+mSoJc#bTbgEZ$BaPg}s9F4PK&NcqUogIwob955idxFAQsaISH*b zvo9~HV<(Vxr3GL+=!IHOn7L+rB3LwoFnr3`J?w)N$#BHsu5_u2}eJ7@I@P339s3y-L6f^j>~1RXn%y6yqL=vDC5nDhJ}@ zAUA+AW|}TbH}@jOnsDgaL8fQI_JARt&%2oI?Z|cX zJD-BQMm_rHSl4Xwz>HTKak_yu4L}@da&#=-yrqhu1f*Wiz4wo>9Z3S`tQ?4$49;bG z`ZH$;z#U~JEpdN%JeFmFP?qJv0wIIs>)}9)fLIyv21suaK={ECM@+8bVD)bx{>%Tp z2Wz^v4dOm_tk;eP$)0_*#ahdIqj7*7gi}(L;FD0I3V?L)4MzW1hA9G0>)S##C3jfz zqw}ylsK#h1w=iUI1|YV|v~PLKI5%vPf}tp@k?DAg zl~q6-p66`gyeT_Z;G*$78nw)QT<0ZeTy6^y0)c~g1C}P{+bpx$D?6y3k)`AxaE?wjd zGic`Brny^SMY?_G9DS{V=MXlWz2$Oh1?14&XV|4>%SeNe1T*Ef?CEMBq`IG&y+X_MpYxhdQVoL8 zBbY(uP3kNz62D0?Q;>-c=rt@NR?JQR!C|Qw2m|)B#G?n&yetj-=LQe3KbOya3`G>B zALlOmO>Q8D@uho9(+tFu{S@wLbE|T*hTMrY9Pa>eo*IcYaJknQ#43GG*6N8_b7iNT zWgY1Wa0NLeSuxoHfITq$pe#8%4n!FB?EUVmI%?7Nr8NCVx7M`Y?>{(Y;!;tnFQhuy zGU&E$*hv&dFGiO?46987gaAe`kh-W}$O}@)yiEz7Lc76Sdb3{b6)fmUn-K-iEQd_& z3wPSIY7otOiwRu*baJ-ymSnAY223N4jp!a9Uni$^KgMNb9UEF|h)%^Az?CC*1)c=k7&|7B3+*}xuDD-ipjwdV#8F$vA1 za(m#h+)oT>DoLY~R(fx&WWCoc)-V1bJ%IRcyB~;CBnHLDp5SN&&H@NNz_kJM+u3{m$rJMhL4d8 zNp(SCLegpj2g{P{0OF55;+n{Epx5xsC_CK+BDysSaD$$1EEbWEC`J9hya`z=b}=<4#NywZ9};9K z?WJ8hXeni;Bw@(^w4JJQF`wyfYHj7{nZ*DUx^^N5peT;_%SkzEab!kuDMuFR(X#S%)EYL747&?EjW|MxG{2I4BO zx}jLq&V2gWUmjnQGf2F5cgX$K63mt0rjdLuLpviT_a~cUDhn7m@tPB~;Ir0P6uh>v z$5tpTGIuoMjMM91n*f&wQyhs5L73lf8;Eyl`rkcD)6eKx)>HbLppFwnMg1B$V~y4% za!`r|H)<7vRBtQXgLp=?s%a^k&*h3<3S}|#Y&!r?C`G|>1|%(JX$w0bNlDy}WdS2s zO+J(!Kzv7O`nMbzaq&w;6?p#|XQa`^GKDP51?)yl{K;e7R{n?4pJnfY3#G7i&@qlfzvZyX_ye#=}ks`3ov{Fx83U})&%Kx{Cyc-9wR;$iPP zV^?zq$t7C^X`VBV+@0Y32JPW>NVO^kVJUki2tu$nyz;R z9u5Y#7QjO38LTat2-=&?X#7$Ep&?EB!Ej_X3o=(F$})?e%X#@dX7q{y&#!DS7CJ9S z=RHi9+RU}3`N5oCmj|oTNl~=%rN3+gaqS+l7HFN04bzy4$peAj|EU&sYXh-UiF&O&gGkqdR;{z&>=e;*Xpi0_U)juD$rN%*Ho{EvxT8Lf$L~~@TzsYx znVUMW*h>5^%gC=3NhBrCzhF(WxIp(}`78E8R_1KgN`J~MzZ18)2O z1%Wb+L(%?9K;6nkaBk2n2jI4gj+YN5ql70LQyR>)02 zO@sW5`s^_&AJ_NG+c4EEjkY-pnJZ64#&|3gz%Al0yCJgORb4>_oE!#xg5>F@7}L4HkgS< z@YoTTl>+}@P~Z;5JGi`Empx~Zt}_EIleEH~w=lqHethkDXm*%@_|A-2-}H!kcU&DY zVA;y8(hgZ3CTA)5@)idEubpW{S<~jDrc9+PqH3DqGg{}RQ`nKS&4at)!o_RQL!D|r z2eadW0n5B^&Dn6lVXJF_?K^m6#DCrS|pZ_iEg~_gZfu zuyI>LkxG|R(GJL_t*;8e7iYHcTjKJK`m{FZTLf#Dnle%`unPJ-0%R8NAF<~Z$O-B~ zlpxZe*9PLchphGP1&F2Z)u-{LI!eu+rPZp^`B+~H*lHvLWEobDoy-R2gXEg_xWBxQ zeiRZqA(-ahf&ro-F^j3)tQ1%dDqJ0UZN@qFEEbF|+0J>E&e<@7Pb)NG1{&(cpyfck zmfp85`f{R!dO)@Qs3+sjB+Qj%d`p>?8njW6dd{ROHWK&!NXGR12ZN>OTPn^(d0dIb zI2v(!t}rReZaX?2N@aQ)!Qv?V+iI;U>1z~DgcOj~B9jRjYx-}y2Z-yXG%daRT%4&^ z9i$>N7Cga{dz}d|J1*q-$$eqb-{&eq`~}(>4z&|0y#8B2&MTN}oY8pzW*;1^kwKVC z<#y_VtT=6Hi0~uK)Sp&>)aO0ozw}X>u5adu>%AADyzp`Ya1Xcz^s14gIM?5KUhuhb zAAZEzhL=JLyd2><$bP;jTmaOxEq|L;#Wh`soos|_Lw0WEI3tps2x41L8jdtm*WDDsmK_6E7gL zZZAi|v&%msoS%dgnlqrr{xNV9@gw@N`Lj9MQtWMgQRY#AngW#`W<1xIXLrj#lk}58 z(|~4P;ToB6h5Lgcg>Cw%HT~b*0L1Tp^Z#Y6Y5Ud973Td|jHMdu=i0*zVfvV$!LBvd!Rqvk13UZauoqFl+DNU!e1)A z3CE?tE)swk7?M*12P>rj!9YI0Lx9pKltJkoS?`09B_O-pR+|3V4aC|&e9O4vp{geF z!Fdh-7Or5-Wn|>f?=uDr_fH4_E!Y@I#+j&E(+==KUMRE=Tl)8$(XQsR)llR(5K4QK zQ*MDixASZ&%uP?e&4?LP;cr1xmPZ7b)|KC^ao<_bor-`aU3L$o2*TosnSoOp1mJ?> z(M9%|EoYvQlqH-eG=zW~J7GQx%56ztWEjFgd}~IWKiBa0;RcdIV)d@P)%DyGdN#OP zj4A~M0Hr4!5R7;-!XHCA9D-T(T#aFnpcy%fWovmG1sJ?WCaAK2OVu|S@dn~r?*sP5 zWL6=U9c{)bd}qjiMp>&!AR3NI*$|t0GZ5o5bnFojFhCKil3p%2B>@N$>B(kPWe3Gd zm*Q*~+C15@$^g;|$gpO?Pl-k`eA?iRfOr75)ruhDzj__yLuhWbQ6mCdQzLPQ~D?S4nP>ifH*Eq z=U|&`irA3wbTbg=_Yz*DxrszJd@op}c->~Skc)k&?O!nna)}rA1;5XrG@eOQ7LP1l zcHC9ZYj6wjMd1jPdb4=-nb7VL-vGqF=>f#$K+K6I>kIH_=gBY)ke)luL5pPy)ya8M z6^BQ&LSXs|qe|`x00b@jT?0vzW(wz;*SeMj2ZQ}{0LTSrl2~S5nc(BNH+gq|TlKQ9 z2E_BCQ^1#ZIhWZCG7c1hgHB@1|8x)~5;zf($pi{v1q3Z5E;X|{V8q~uWj7+u9(@hG zZZzwk+bnWFR|u$mAOPxH%$9yrAZ7r4J+9`GS!~Rn*O*_qU?uCdp^OFhPPFQ|rD3*D z7BuAZ%^Dftzs5&dAa&Y}(TZX-9x>$;Gl2D*XzIWNB?DrGPAJ7rElas%n?;39Bz!e0 z6^X+WeX8J_LoS$Kam3WqV%HZ1Mid0ZTr^N_-tXK){LGX6X0^cv1ZE1RGk{qgn?ca` z#Jyp4jkl;bEC>MSrRe~O&5t+$+}k7b^vx5phxv3Kgz5rPD6*FuXxu5U@%-{sv1_HlRjC%{v_&=CDYy$Mf*8ifS=yh&XCCI9vx-auUM z&!c_is29G-YJLGcsHBN`HxBjlIV>GL{>CK{4pPCz2s3fP-wCjk_IKN1c?Q|MJkIM6 z%f+%u@jN4rfq1^xxSOiB7y)( zTk5$!wPN4TrvHQ>496nxU{D3Kx|Aq@VgO9sX_)ogSxDG|f}Zk+CH>0(=5PPgZUN$g z@udlpf#(d(qRttfL%KI;^mO!4qjT~^Q~}pwHX<_!dZ2Aq3Jy;t>2yt^@1NQYYI?)x z+Ql5<{C;xA&l_`h#JhvT)y{~$M|^h7Wx!Qejmx^G-BtOQz^pi`FptTF44CW{4uA!j zwMo1J!+Byb0iVt79{W-|M!u;cmF?I3L5%clD$xo&pzBspod|r2z4u2!2E3rq`tj{E zVmDrySHOB(L~uglrd$4kNLLD$b#6hU)?S`ZuOKB%O1!)akKkP8OuD!iPQ!7%M2VN7 zq}=P3PY&>z$*orgE?lk6KFdVf06sh;-auUSzE`W^g=PCyqv{L%M@H!!c!SStAhoBZ zd_6G$gM1|_V6_{Qy4X+wK;Lo%$Y7()Takhl_q3Bq7xA2N4%{UE!sZT6Xrmkb_CRbG zHW3uiQwkXa1Ra2sP5PDmpq)~dm0rT};c&k$S*ZyNk~6n|m4<=4EYOFqC% zEcH#$vn2h&zF*C2VnFLPV41}&lz&}Hv+;q`R4B%wU@nr^CY&qYHi(bX^c_I_e?EY? zPCsJHTF(^9`{~8y#|%01Y%m5UA_uiETXSOOzNNyRFkXZav%XGA8p?#}D7J#kz4V&0 z??|&b<+DIA@g!M?_(=hg_N?OV9ecKPmxLY}v3z_9;@9H zQ8@}^ze7Pl0`DOzU%}@!Dr0wAMAfGrR^~$DL%Y z-?&TDwcahInJmgr&J7Xg{ZZc3&^OyxsJ}ug22B>}$VX!4**>(LNDza7-$PJMd!`yG zTrsDQr6_ariAFj#*d%^BNRL>R0P(+h0C5e)v;+vv&#`atz!Z>l2EmMd$x)J=lb+!G zaCiXIWJa0G388!?1RZ{S%L;fdnb*Q(D^SHk?_&7!gbhU~kE5I=#ECY)4#2)M;+uhZ zXT<#QGLXjG+=*!*jRnkjny^xTOKY&+{X?8ICuLXe{%;I zHxtCb4NgB|Oruhn0)QsLIs;;?C~<{qUWwwvawwvL@{(a%I{pM%JAA)T3Q9+h1VLT! z`Juo@62?e{gsvVi8HCZx7_wo1*i{h_BMAH7IdfnlobRMCLwZ{vRyao-M$Ktdtyf|< zWhpiW$xA6KBSP50iD6ti4y6I-JvE`}6o~%#`evZS2H~)a1r25kYs-}p@2f|0rRA21 zLYNI93N_OLqUTwf)+TFJ#nBmWlqO#pFR)oKZ($_@&ffP&;MiP8u|Flx>_s_SL6+F< zB(#$ZT$SX}rwbb}g+E`AAT9>t`!nJpKr4Cn z=3Ce*EZ?CDwaAOclyNi(%Hf6O=o@3B=#Xt;{rz}c1r5U&1w)3IR5w4+Pq>YeR=j> z1sU)FB!2e+?UE|QvDCF%B`s2=RNBiK|Gjezv6j24!-4c zQdvKixeR!jP)b4Sanf}6Q z$hRp?YnP^h*o=$3A;IO5WbG`fTbNVpEFrbB5M54zWV3za?)zgix37HfspWe=XFzZ% zI}4&sitPhHFO(|)s8%Y=e1VLHmB75#oPR$oY3Z3TW4*mqYt;r~>7&-PWdJr7WP|>8 zV=f{g2CeZFL$3d6>r`-XaqWq;B6}G|c4ny~RBi;p z;w$_PFt&+61%vT4a7H)wSI&5hzCG>1B(P6LhyI1wnx{=25_yM0D1Bb zBJ}*-#zZlp(-wlC5iGb;kn4`4zciCqp+|fd5Z{&MPCuk;cL#O4Md!=U^+hHNHt?Dn z$j|$${wubYU}!MVXT_SWP24}=opK>HH~ydZu=5~^f!J#kF7~2c&`L3eZ6N+P{!nlF z5W(SGgq@<)oT1#yP*%r%jloj;AqGi4F4uzBgaY7)X=*s1&_LP$9=S9@MD6~Yc0O9F zEaGKS*Iddn&TLum1MCi|wCn>H*FfA~%-dwdu-yyMJ&%edF&&C(Rlt>~R7*%t>TSj# zKQ03r0N6Q;vo1bSiGa13?JDRXrgn()zF+hHPgpW5WZ%TFwa& zd-ll;S&u%{&nWTd!hirS1C*fN9*F1JYyiEW5$OT6_kPQsySh}3^YFVlozpzX44=sz zuQzQ#;iIk;&+m-l=Z>By-f|!0zI>j%BaMSo{#=>^RN`zjH~j~j68T-!Y(27y#RlTd znqKcwQ<`$*Oq++dr$>$|boLJor>lA)bCMH%S0gP#c{a=1q%?8-yi~p za4BRD9gg)!5TzimST4qsD#48f$3 z9S1zc!rOfKtH^7!{ z10Zv;9Selu5Cp_TXQ5OzcmW{paYZJR^c9!erAW$g?@~D^alZ}S_EO$fb!v4TWq>h; z!6ycChOW}WYgPGL8Rqtr@dCtu@Bzf-K%4<-h0bS;)c$Wj9F85;v+V;A5(lG*AcD?^ z&RIajM!}vUD2g*k)=iF?)g`M)M1n#9VgO`{i7lY94P|WK7p*nRvuXI8l-kfW<=91J zt^0vkymltJC*l`uN4!;;w+p+t2sS< zCYV`mfEXr2V002v$ICz~n#GYid2Ch=R9@hkB zEm`o4X5glD6^tF=xHhl^-p-hP-web*vw^tIjM%WRtZ^q*|6YJ+nxWDvT{di&sFa~3 zr3kHxDsqMpcQd2KMRnzcNpc~h9p*E`oCQU}lgxV2hSL<2af0kWc&Aq2O|2zt$L9Cd zGUC7B6Aho-7Pvk7f=z%Q`>j0pQ^`v?35$BX;SaBl|KT6YeN?cme*#FB{c>Z0k zvJ)9${y_^J|aI{*$;N zH}r{_wT+#91-7o`e;;S3)v zEcJ4+lo`zVLZ(^)`#H|={14U}bpZunNj)unDFxXlxG^CsIqjL3F<=->=l$W%g7apw z)&}BbplFcVRXEJ4to6qEoHbn*?E08dnc&30^NH()JW+6~ts?*?*zsSMW$Og7<0a#T z|MDkQUposX6|YQSvLxsJ*maGkY6h-Wu2X(m)1GY^FRgP%oPzPri2vXz=+V0u^*@N7 zGMUaf>GhtZ7gI-+R?k$rY>-<#Pxw@1F6B?Pl#;UYY!*K+p6f+T=m3P;M?2`le2JYd zj$}}48bkybfCKY(2rBH48;Jjodw^JLm!|!y;d$}SuueGEHE%=Jo9S2X8{xCRIruV? z&PiB&UeDTPdL{(}MOta6i@7#=YuX z)hY{im8oFuwN!6;*}oVxEZviv7G0EUBPavnd1?BEHLZ7DYo`4BHdb@zP+E0fhO>7; zXe0Cz_wk$&{$l?=D_aH7jwKnN@WtO&&y}^`bNMp_FQ8$DDJrAeng|lymh}d?(D)b$ z`PNJ2JZs8i%*ufH$cU*#d;i7}aZ9;NoJ7+qb;xlLe<;?}Ni&=1=d2Z(KViFKmQI;G6 zqDVLg3;8S_kd039j4}L(QO1%%50s1o6|)Od0)c}KugP-zw*ul{_W>gbi@y*=9hoT+KE2#ia z3lo%lUYIj9G4Q3aM4JH@uQkG2w2%C5{mKUr*9PKLYu5LfOc5(RviNKaS^0w}2k=!* zJzhb@2K-W`FV8#+?~RQR0RgR9cdqm%E_VYuQwyLcj*79WF}lmv!;&SG(fcNN-f6Bl z0b==}^!-5md5^f@rQ%Z*B?D@B-vF?9HY+;d_)Rw`ccwrUC^xxCi3eSkUxnRsdmdm7 zug>(D2jgqwL%5D~TIS_os25*Z8fZzIFEUKn3YH2-y_;+!S*tbe;xA-11&~njz z?s&mWzFd|vJf9${gp!E;qs^SyJ!)0oF@@zoX&8XdMq9+?8x6qHDj*{uR;KndnEMS_ z^gWv6yt+0J|93ZL#0n)Y>&a>+Kw|!APbr-0YuTfh!5p+(@GVf(W6#=F0Mlk|>+8yI z&K-{o(7Z$LyK~UQ>y;6OF^dlXiCa^3H4&ri_skN6wSjnN#6vZYK;qKILGThYVOz1( z<)8sZ}V?dbwCDp0+8;1;A{z6Vir|@sA zM{CYl{?DEemOQw(XT&f6OpWskpB-j5S7-jtvFT8Y5VDuiq36D}dj&jALBf4dxf)v; z3K1Wg{J|1%_7*_chj+)73C;VpUSp7@7wD)rS#$eV`dFG?I)S(oHmxV=T%ZjLzx%0V>;xky(^4U_9h`?a}!Az1%;h?t3V z7!Xt9!Xr9E7A!wpoM@){16V;?D})MBgL)7=sNtVg>e_&_C^&VQ+ z!y8OfvhUT zf5;in7o|ZFBbKA94aE1CrmtQ!5u5>c@`i0>V1R@S4bRSjxT}U))rV)O{ZJ(z61vM% zn3JLD%&`FdAczRpJecK%P3m#1mN5PT0M4U<9*7m29tN`PVDKHNzrDWF)Zdg4i4{v- z3f9U)`&?#8W@@4Ao0N6fjfmhK|K4MbAee{pZ(0FRg*|SrgXiv0Y2XOg)1TLS#J6O` z5Cit3LV3B5&G9HLl~J^TGZAc%eohS<634bfbx>gfu4kLEdFCIYkgpIbN~rn;awAU) zjTA^HO3mMMPY;*JlB@-0Kpd0qHBf`m24ZbL;)_QJ^rCUIjHX|P-Gve0GBZa!Q><%5~^^y zi31b&n2<2(i-+tZc?gv{axuf3BSFiEf5iicP2%!uGOy{=k}vb%ftUj^WI63s4CjCy zB5VXhvkcqIszv)cZZMGZT4E69Ibjybkp-VYW@=hY8ADomMhm|Yp40a1$UcJ+VJs-} z(T`XUvew^ugR<63X?nXaDP&DQx1V}|jm-Q^s%s3K_!4`5>&@WV_#h`@wa{kjc5j+W|yY#0ODW!zyHEowR^<;AkU>FdlL{$uayr#-J=WLxYATGON&hZ z42UDtQ|0Wy)HorQgbZKz!t`u#E&$?+S<<|I`R-zdG&{}Fy5LLSKy1od&;ST{S!2+J zYRIL&h9Z7Jjc1No#fdguX3Km`KHh+kg<>6!e(t6Owui)@0MP>+2h{A^q|aZL|xj^a%j+^9iW)eKwRe>v2Nly z4tADoIFZ>63P| z;mj}x2^)xSm?M6arltBuYx*RoboN2Ep*aRzA32le zaiezGq>>hRA8QFXOK6{X8xa4@uX)LcwSm}1Vd_hf3^+h67Of_5;|F3c(a6VT0T6lF zxy_{Vtw?ldY~oLIpLsUI`q2(!hpxQMxOE3hRj4@l(>x&Qj3~8MvD3P@6}ic*YX8Xm z2QN+kYbRNYHLtu^Sygu0Bh_^~rM*WUB{LASi_T$m#o>N#h68 zxm+ccc>X)gQCIVi}Zi3B85S5aGv~ z&T|_Ozx0Up35eC$gZq!mDZJ2UnFB4iIKnKGOC4nTA6OMeV&q!VbL+ZL(+M)r0^nCJ z*C{>YI)>##I0Z_2R?9lJ>={a2BjFB#AfHw~_Qs|Ta11SNAg)119QTN|ftW#Pmc@>l znM1L@Na<-G9*%)cB_jy|z9+;jI0*DXgZl(Gk4tw5y6o&$E(|#@r_TX6wKp{R+mlpi zN{ZYKRg+xy&yfEOSkn(6*3!<1=hBp~e?SXLuxXnBz5@H3a{TAMfrGD!+$|~0g84y` z(2BtTSRt_r9AlJu!wOh|*-Wo;(pO8b;PZO_uN^JD#B_Z*&=*ubOSq4tvAZw*_qPD? z|NfDzrSfC_B?I=i>6gf^6iFtS@ve_HN0|L|B%1}w5bLNjPRYIFN z=NcL2^EJ#fVIJA6UUVv|;ulK`kNf01BmVtB*20Hykm=Gh!+)%iV~feGsg^ig9rR+^ zqc~@XU-uVOVJS)i{-R~`@Odmrr?Qtg1qRo;2IX={G8+B?WV7(un55NeWAlWHy^yp8 zJ^uT@>ju{Jule6|*7WNDac;6d2j70q%>Nu{Q0EZma-ci01!C4Ao0yf}HDQ7)FAOU5 zTTml3Vo;nLB80@irJVd5*uz#}^*S~}m>(|#;;CR(#rq9FHi>=;j2Ompu58U1<9xdh z*F=Preg;oChCRO;Ob`g9Um+6)phk~SxGp$hBEXTClb<5B5s^?`cX0#ppZ@I!;=3G< z2IEpvtuo|*lpB63;j%@t3ve{uCeLYJr^Qu9(88AGiX~l07MIV6&oV?7Ga#j$0JA`` z?`v@n2-y?>@h>}o7$ZRI%W^lk;bqweO8I;?B7=o>TD1lu&@*Fa&oHy!AU9qc_M!sX zwgS|MXLxBpX!r`}2sc>6m{O=;+X3-h{*nU(c(u-?EED%Ft-3EG{?!j4E(7Ag)B&`B z?*v;WXmolB4IJWsYi!>;XtIrbAqQgiVQds)TB!>&i+eV6APxi;^76Ng*$nlv8FdTHVZXiC_v&0nP-&(UO^_hA3F_a3Q%c<8(L|C%_VN8yNi#8T9zTV)AL=v&iloOx1m57J3$QNipLV!H5X37Q^x|=oLV4A;_i0 zM`tmjM9NW~X%o=Fy#Nr-+0G_@0w66WH7LG*xR^N*=RIN>5WoF4_Y4kzSdGPCQIHGy z^>b4i)%f->%XCg=;!zIHgh?pv^yu|z_*%t0iutvmr#n~q5K9b%0avovM`6^Yti!Sj zfcOTwBr0-AwW?g)v_7p?P!hwHpmH43%Vd9pUN*7xjAe+fLIJYp?G;9?v<1$xl~kQW z;Y#5&hM6U>`jA2?JkF%~W#B*zcK>jEp~PTk`fv+b>sQ}S*2)8Mb>=Ns*=;pPP<}QA zbW3n$8w(uIU4n9Tu)#g(M)iL13O2xnF{Wq}m+j{o(sVj`GUDf(yK>PDO)<(%AAae- z^83G2yGLw4c+-aQmuthX>(_kfdwPS0#kB_WjBFB>NL@nUof9Cmv&FmwCD!PiS9UV< z1psMf{c9G*;OCA;f#Un7Cw+oLEopGW^O

{xxL#_No7m1F;1~Ftdg8y@j%qdk?m( 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�bG+*9m_0H@ba%VSm)1E3Vp9&98hxXFr!(dORm}T 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%0AcPZ;kYsmvcl(ze0}xkMSEohWIXbGq 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 z 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 R(iFT7D<4tH+JD^d{{VN_m{$M* diff --git a/current_view.png b/current_view.png deleted file mode 100644 index 7b66925718b93e396ab7aa2ef92b787e8ad0dbda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77982 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 a(47mFQ^A^VsNLf#H3;F$743cI~Pn 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

~uf>I5%z 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 Jgkp9p_FtX{){OuF diff --git a/customer_card.png b/customer_card.png new file mode 100644 index 0000000000000000000000000000000000000000..60114d25a9d914627769057077a136104d206869 GIT binary patch literal 55740 zcmZ6z1z40@_dZMr2nZ-hN{C8Gh;)O3fPi$@fV6ZD9ZI)?bb|;uz<_iN-Q6MGAl)&< zw>_To{{Gi@aShBo^W@rl?X}i@--{4Mc_};`3LF#^6g=s-ZC??SbTVdLb>1~DAI3UtGfQ$o^^4JIJ@mQSm*CblwP?begDawk|%}| z7R(kE<@ptP{rYFr^TX=#arlM1>Pp(GN-#-l3Bx@l>4JOg4jldbUHts(S2uhPe1y-Q z1=rI|tUfub7hM;Tf~=Q5^d^S!JrKJ~>wh2ovay$Wj`sJh|NYgEE=ss#OvHhq#6#v~ zuCThCum9`>e$6OeRCpd9I8WLPdEk(0r+;ABv$UGj@k+2McZWGt5n zktxIsh%VjyplMyv%v}u5*tFSb+}*bOd7tUub1t=)uJ9NCqC7u0XICwFdzr5ifLE=Q{#hf=f6tgBLGMQV?}1J@7#q$Ob#-IPWRtiS0gx<{@-f_C(FQ9V&cu34J%|^5qSJ4 zVN){TAjIya#U$N-SGHkdv*mU(g*|MwelcK$*ri`0H2C)kcvDB~x-D5y)&CunR%>yz zf`1<_*dbFlQcSl&UJl~9`#gy!BFka*)u;tANm#)4wiQ0LK4y0%do6TZuI!}(bCgL+1 zAG?MA9i}Qg@_RS;YHv}U+*wh!#)}w4UhMvyt_%&y^a_HaX!ch3Cfwjk8ky!pV&vi9 zCBn!IwL9)t@<$fxEEj)0xZ5v)Ncy#S@?o%8s0VedekH&PNxY*{zN=(f`3;C@Yd z;x>-8!^z28%OW$SV;C!}rj&oIS8JAkra0d5h(e}dm@Jy*y(%13bU)q*Y@11QJGyyB zcj13$5^Z+RO7{Bl!OGg&VfR*cSouIz37qq)2}`rV8{LoEwQTS*R(5T$_`Uee*%}$W zLi#e~=5%YoaY9cI8wW?MaQt&jjGVSM1#F_gWV+4U|^OytgFA@bZ^ef^YS9z>Q~M5vU{563hLa|{MNhVQ(WXJ z&BB);sRtC==)PrTT;A8#Bt#7tiJ8|P2c0e1d1}n}VrANB<=VJ7)~j_&!}rQy^_7^oxZ6bzX`X8SlNK6Jl+vkphK5hT z>9xx(*bUG965i3zAclYJsAMR+&c{ z`N{!ZqMm*Y4WiiC*nFvW=krhhXVa?RV=z6a-k60N8P7IqxYm?Jk$$B+_vU7f_G2t?d@~CBhg;J{#dp*8d98;Nyf<$JWUBKs#LZ&h%YLRQPI}MX*O=< z(zr8LOAzrSTV}N$57S4$Wmm>R<`;3pGcZC;;p7d2@cGAQr#Z7$HKS-&4X9q^Hw$+3 z;kW)LTN9+(^F?}s5cdm-@AAprGyF$Idrf%u!c`$5AtslYALff#-Q3*nc^G_?!u!y{ z!@7TZh_+CzF_-10icWHUfhB1~?{z7vxWVV*T;uot46n_!lzpwxC4tAW-eXoHyDG~mw22}2jcl8aFQ4C{*EV_Gimg_acY1i zOZo2DNjV1M)xrn4oj%aCXvEMDul5U)k5*!pu3xX(-@I^v(hCcxHi^?bwn0KJMj!qY z1bFoAe9U3cn=Uj6ng-C%2+Z`XX{<3=9i?}-RIUYh=1F30)eCjYT}bZqp20l1`k z6V=HbWn#G#hsbRcjl#u>gu(*N`fx0%d%14!nAKnC*jr*wHpIg-gk3Qad_0s?owlCW451lK_ED|rIo{2*77>QT- zGe)n^@Lp#R6?W_&*j~iYswH`SobH zHY8?SL65g`p|VFpW}&DV)>o zNok~fs`-3os#Jrf_$yzJhOO!eU^nRYFSTtpl;IJDQlp6%S=m{WRo>Q#+*|cUD9N$0 zErNUDtA&;AuKtr@EnDCN9W!(8!xw6cmpxh)1Z$D0f}4|>=%r>i&7T6o&R34$Ld}Nu z0YMX(2D6uV5=Ha1E^Mg+&foNEc>>U^w0}LvXK=6QKcf-4_X-N-fsJ=~6bQzAiD)x? zU8wHC5)~YbV&JIjnR&D~?c?jQTb=xSOdO|BR$AKRCOY=;`M=BDa|}5PS&QXBxd|$7 zbhJ-;ZtthnqXwbJllDyKkGhZR&3exRIy=p^E3CTwo(BGTD&TlA?|bbg28(oR(}X>s z`ztB~x@Q+SyC>#ipK50c)tP~3fGFteYrw%F|DIiko$LxMuth)LzI`mf6bd%8to^+B zxmbg2gishh;X#TP90)!`Ue;KI6A{LQ>iVqplA|!C?FDiJb7SfO+!iMFAq04Xug>6+V#|V zfim_Z;IrL|Sf!*mMnlhO6Duo8Y3z9wA3lx1+oJ?^uYoaEXJc-AIi6pEK;iEMc7vIg;N*Xlz^DZ1EcUT@(k(s67 z*anirL`lu+CUoA(Sr%^7xf~UqC6_F?@v9uy`qxZTD;xbmTct{E9`?PDr6nWF#3_i@i4{ zbk)fiE@_m{WyYFk9r1w*{gsRi$55Y+!QHzCBWtX@D>o7C?|=_C4f)@>Jl*Q3t9BG_ zcKTpHc{FodpG~}W{MOTLr+s0N5-c%a zC?Kfg1v(0Fi13HWV$@jltI)B6%E#Np#Du6!DSH|^Us(lDOIL3ptqZwkGag1&$XyYk zixnbIv|mZZ31@UlCW8TGSLpqIN|HZ>OK?LdI%(a61vR-%8N za8+BgPjQKd)V5y5uZc$d6-uoOoUQzb4Z@cW@!E9`(zH8K*qO`}&Q-{Xn=)^WXUf}T zIj=1+))q%TY^pm&t27ZzpN9T~HjS8igZ6O!mlP5AD7g|cG8(6|3RI#M4A|F)8Gjl( zPVt0>Hyie|jx<$#UCn>ofV`2S#K&@(9gCo;Px5Vo+uim}lE^8-bcuU(Dr%bFgEaGa zXRHko5hdTk%!W`jKAPi&xJNH^r+wu8u4ROVc5l*(C-NCJ#=^oEBh5l}CiQ~1>(D|R zDzo2g4TdF}=2Jg0p6(u{xL#}xh<;Pfk$J|zaCH4O(L^1>>76PKfl%*ES1y;Yr!$t- z%Oc5sm02*Q2mZV2l2T|NdHM=#73^&JPat+*n!G$$Vf}_FfLJ%k2bsV?kPtKQwQ5H* zBQA=NaOky7;Rr0BL>b+$>9%H4LYq&_sMoxl@U7KczV1$7So?9;Ba-0%~DO`dF+mc#zaV_ znK%he<#&G)k;BYe>~rf)!DH3Ao-VhLm>W~9eUzt^jVU4`^6c5O`_$CC_K`{i5i3Jd z)N3nUF~%ll*&`O%xUc|q*!$IPwG;uz_rBQ(kF%iyEv|Vl&Jd8Dd3t)1A?L6PG)qI| zbX<-`R?^+BN~GNOKwa!Q`u1n3<6V6GEQ_%pD=RDeO;u4nWp(O>QS0mJxrh5L1P9xt z3Y_MHDDvqde%gi21~2bZQueln=pQOv$zQzr8~7{BevJJv=YMe}EalZ;&{kx?tzW%*d4~6>x2AkcIB7a=wEe`gi^p`RUR-E-XERD!z zh6fEMj|d`MKVyRYf<&DtLQ>q@TSPMZOVzn+__`nodbs=c^LE57k6dA%u&MsA~HnZQ*Ei*1l}gNEVBMq>`6-? z9<7j2%EE#Ha0GI4$#M96AhAE0hQB$qs8633-#~)!Vr5m=t$d1=eV}I zT!{z~-3A+f!317g5()FZmrm~1`B9Qq>^fTe>u8wx6uo}}q0Npvhy@$=o_K^7l`9c)~^vj1G1Jj+;vjsItnzj zP+?Q$*VHG=(&2wNT(8F`*vqN_>;c6 zwn*w@=Kpv88DkA_t;(pa!jEb)5B>u&7zzFl(D;nP{I>wVPx$W52Soc}%74fX6F@W~ zAgmbn$s-$MlSgpdB&z=qk>KQRtCFmt^NbM~UB*n{*Q)Q!L*||@b&A*;ySW7ZS#ZR_ zKAZmA+my!w+({@j&p%Ip7M_=ZLi?Qu9G?CMRoKh=YsO6BXy&|ZHIc?c{13BuZSg;B zLpOGzTlwy-5}3h`G4t?qeDtSK3%G;p^~?r!Rf6pLVd*NCbCGcw#r9dTAHp#O(ziVcTaA*{6e}9&#>)2 zdgb;>!`b-Ox9{H%$IeuQ;*dyWwmR&9_k6z0l9^GwcDA1xUf+J4=omJ$JKA-p!e&+y z?twLXIM9IBOnk+-Tswh zul_zMzo~4sw>*z+WQ#Eey4`we>T1N(f97LRSQI3v>uVVIfV<9o?#SZf39GjEX97M0 zbdN}-(YEQ2- z!;8X)?SC=L35gtaBM!40v)u{Po|cXd8W}PJldl=ds&sjTD&FUz*R@O1{LqANEgqWX zp0q8PC#+?KnV;B1y#>DrPxF59Jiae=JKHlZXX&~YqO@xb@ww0%zd`)j|1CI7+@q3x zy)vK6MsWhs$Ez=IaMeMBYPW|tv-@p~mMc%c-&IwD;(4*jC!hLmp>HCs;kza4q^Q78 zR8cP^1{Tzo_G05b{_a%ww!RO2mb_+3k6BwNu_gA^^r+r1i% zvW1WXFH&v=z1+{}d3ocVx5i6c_C!Es`O*|}K5nw{xk(icN%caWme|Y+fOxX=tK4MC zjA&*Wz-m*aE^H`41h+lIIX#*+PUN-Fy%*({80VKTwvE9|VJreFLl8DrR<$+<>xK0d zk~FtDx=Xb$mZPfkRGH>nug45`R8lyqCjXS=1zMcY?U~nCajs0Nd5ysjS}sfuD}|8T zoW>?5CEhKV{i!_}TBYXn-Z#>jM!m^Lk7jG<+wxU%0fI)=5kaHqkR5F5B_&YvkF{ zqzc>D-{mAA>oA?XZkZ}G!CLr{GgEwOOTlgNW*O6Y%41<3mZp)dezf8U9|p*g#Y9nG zE)Ajw7am!^B+~ZWFH8*}uO7QQ12-)pD0Ro%M0Hn(vQAs$v5A~!2ivl?Rh6?fAMh3z z7h5aBq4N`y?C>D~6n>XO2E@<1BE@{u`OQJa!V$U5&*}}I$;Wxz%36W-um7(CavJ)>VZ#>-*oS`LXOo(myh>oSi_z;+* zOmm&}ypc?Fk?8Pf=Xh^ED$?3Wru<%o?ffD>eBn1NDczO<%&^LK?}d_`CchvQ^O>ql z4=p6rx~r$>sdtY$=IPGPjf!ISY?Zyyzk}GVWXDa$lreI-kfUl)2?{iYv@x+b_Ly)- z^7v~bE>lAe9eX~?4JUVy7Vay5_?BV&IR7In-Rsw{0Tf?scoHbK75VJh;14xs3T`Ki zoW{mi;gU~pTdmo~OLQcq2nm&mV`cF8P&GIg9@!USMAC>t%%@XQtWcZ;^h)|+ii{`; zoIcoRGqXW9vo-9dy~zceu)E^(z8@O$2;PHpu;C>plcl(P8OwzVLvM2;Z>3i?#Gf)|ZuzBn$&lzme4W z>A4LH$;3>qx+S$c;wH2Mx!JK79GurtV$`8kZoy4XPQEg|Q)WF97#!@*yRAKItC(A= z+QZCx%`)$Ei*8$YAaFJ>8h+vPqSdBS--TRv&v|WiHEYP+&8XMoP!U(D|KYJ(!=&#= zzl*gk=|5ZvDms>a#j0$4q;*+s(^ILM%YEBn0;~wT=Ch&S<+6%u6moF9 zXNG?AyGQUo;_b**^DyeFe9zYYwweb<^)h>8vui-ZpmKG#_~*U5d9TJk-@o#_>f|wr zNlJMqDH+#s8Rk#SS!{+A!L5v=cc;TXvR01VzQEVS(G|R zGW-4Q4f?m7oUD;e?0fgVDYXPXqm-^%Ga?`xOb3HM^LfJXXpPUoQLn+Sy}jM=CD{rp zvDn~hzl}uTL>QH<%#x-up$oJj48WZ=GgA=(xFiqR9$S_zl#OuJXq7Z?wf%m>)tnMP z&`&izv@zCJBwePp!}#z5cua!LD_=Y?DBxj*EMDP0dq(%u{lcM6jl$5#2-H@el=;nC z+VM63p5GoG-h(;%`8VzH_E-#Zm!^G50(YvR@|ZK=&ZP2B_Y(>VF7w9aDAC^!aFC~) z@~Wyt{i%W-1S+N|_Zv)b%Pq#1@)^<@9~yX_2(YZ*mhvX|Q8yJTM7?{JNj-Y=G13T0V+e7*w)jCTAa}vgkkWzmH#WNJ@t+me6C$R4X?@~PrIuD#S0VLhjSX~6 ze#3|D31{YGKk2@FiO{Na=l3|$7 zk-Rw36m?nv~}#cx{gch1CI7D%%RDJkFWB+)*juHDBw1~_(}2HU5+JjOp+ zx(KD{K~hn*!)o{LzE{3a3=YAMvr(g`o3pi~AZa?kyo^pySFo_4;56%Z3fhJQHNYJK=qUSDA{RQ&Z){#L!f7ubWdYWLFl6#2ylk&{rtBVuap3&a9KxkXX4Yo_Wn^$53|~SHVE|~Ir=D;iOJGd!<*akaV#!KrjTg_ z#Y5wa8s%N$6=&{<0x8bt=M0e}Fqd>?VY8$062L&4_FZlLC?8%+(T!uh4tqcl@y%^Z z^XA4heYFYfIr9)L#U@O9yBC)ah~CP|J_Tt8J3IUN z`MK_acc@1pZ}V36{BoznJS8!sH2K%>X=nEg`e}R&%70Zo<%xq@Pb+6j;+^hJcZ?TK zulP{Yc4tOB;zb>V?;!%lXB{iIL@#`9rZl&gr*@zz@@AQANL41{WrlZ;Md$m$>>DhG z>{!;!g@-$$+@l<<)SM4AA*(Bw>jsTfFy4Njs_?*UiTZ5{2xewQgJLok9!D+Uq&1x^rL*bbSDk6VWJuzz zcrfN5KRB3M0wW$N0!$oaO-hV)pJ31m$MvEdcXP^p zSVyJ4GurTr(uH3CjNQ_#aM1g*<$YdlcQm)hpsO37fgqwEYcFbb?&Defxw}vL5!RP4 zUxc_Eo|RjUt!>%XBbG)j(-p2h`TIYEZdVTye)t`wKZNjbxjf$O0}e*U38e%TdyZah z#kX(YNGvGyl)>@ea#FLLYhO_JuocFVhy|me2Fj3%txEI-!bkn9UUi2o16JvQL7sn` z)5$yu*kcO(CI;X=-HRV866VYXC*mXE|DE%}Pe}=s;PrZ=B?!C7TZEKU9!&frs%@iB@(aU3bXGg+L@xGFhlGFAiYETPc z;sYAqv25TR%!I**%RW3T)Tvy{?~vr!tWB47Ld+|q$pd`BZDmo(Y14rLken+EO@1tz z#l@ZK91`3UvsE8BXKP(@4cmMF?&5yKaBWY$MLAf#RfBpAf3mc`r_!aqkSfld zM$>XavWK0)L6U~->l|)}XEQ@Zezj2O@?|-o|L=gv_&mm^-L$;sjIHlMNRJ%Nq^!8r zGJwUWvR(26HGIv|?Y*9+Mn`u?uUfB;NIcxUSWnB;tA#4x7yJ4YrVgj&;E1LcuBk=0 zsHWO7LV{P{k;*XC3dH$Q&>zy;jzDLQ@J!eup5$4~b@GrKd?)=jxr|Ds{qryn0xW}JrJzFxO>TJ&Ayn!M8IftFpD0a4ut96+GtM~f) zfI1VR``SgqYEux%3`XabmS0jh_Cy%RdCl zvmeq#_lP|YPEYB9X;M-s>Am!v_658yxsKKbI?`;{XQ~~yXDidyws?rQJV(4!#V20q zZHyN(r+Z$}wnz&7PCG5AlK=kws2P@xO2+NUsBkk+#AQC{J0L1Y<=RUSRjeuk)=Qq5gq$FoP{46?%hPurbz!o=B>0Rgy{*2FBTo=RY zqPb`=^VFY~4YC)lyt{nPlro?Djk_2FsgIizk5en$(A)iznR$8^ek}7*Z4XBGQ;A4E zRitmI^&8+^Uhh;kWTJI3#0XosEiWyFgoU}>3VHqFI%|Lh`nzu5rr#?D6mUnH>!-c? zi|?Zr5VPJithVhfrq)sN23gv>O8qMdtlKl?pc4AEKiTqh|L{~YcckQf3ue@2%?neG zM4|61&-2&y-6PbPlLqruq^QY$(dgWiNA2H?jLuK)575W4>AhQSk6^p{`SOGzD?2$l zEv;YQhm{U!MuaS>W0};m0LPW14CS^^WplUytJNROK#oFUe>5g)q|j0$&HJ}BC4L8Y zR3nRjm6z7LYLKTBLSWDk3j=2nZJ-Q!O#A#n*k*TmI0&u~8H~-`;e0QVMu;q))#dXE zGAwnSu6kG1{FIgH9vhx0bdOU)Dq;!aY`nx1$NbNF@o5`SW>7 zA#2Ws^LYXaXHLaO{<6q0Le>@!GC{pE#H=cKrf_n-rgrjIKA;>zzHd^`@#pk(6|Tf{%dCgGqx+vDBS7D=P^|(Zm~W{KyYFW9kkY zT*yG?j!z|^dQMt*5TWC31+(ZnwJgXNMy7U=1y1}B!Qe@ni z$&V4T0U#0qg|QMmgiuPqUD>A7S{xp4j2~|#;Y*I~ z`*KHv7cX~2d)q^WF*Z+)!pek?%H*)i7@vyoEfORrlw)O`))Z3Qbl;ut1>sY&lEnFTM((&s+Uv{!1TgEoSq`AtRC%o_1yL;fa-6FV|)_3Hb-*7dV%l!dz2OoX?pGe z(~)Bl1QGc8vqc%3>HNhDs_BQ-SDPI)Kw;SpFn9a4lm{{Vq`4!1XxWZvmD%b1F{a(> z^<6P!I-fnEFH=*1)PxXpn+ydk*VH{Q4ZN(LR2Pz8^^V9%u^ybHx9jWcJuUS9jLI?& z93aZ0g-#Hh_V2np7|H;-6=mIT%3>5Z5@x=ggrB8+PTr$4(E?5zFSQaASEj8e3)<2L z)N+OwiUqr!@HOrF!jhAsB|(i{b=!m$67pg7=BU#s>R;mP+++1nS#UkgtCro=co1Z3 zXntt$Dr*D9$AD68O8C+QzLCAYL9ALo^S{5;VB8bOdc3`v^vL;OiH>(Ag{|(2f;sNR z6Bd>N&1r??OU}*B%@i30hUFRl6nO@a7ScU=f(9GwcA57fsIqX4qLhC8B#U}WgLQS@ zCn{rN>%(A%2!LGzKCOt_PfJTn(>{CVU+%-Ucz$5CvcArFIFx(w{*Vi(-{dtl$#i%N zR|hl20cT0UYvbQ$*tF^7PHEXaAZq`6^!f1cFd*vBAKl{$32$ArD|S6FbOR#b_;UMe zZ6?)y#{p~|Ro6l-7>V_CMK<8|=U5p7X`s84k&tD%5qhPzIhUh~+3lGUs-W4| zY1QZDaHeun?LuDDu=3uXfs~k@;}0XjAs-}Sz5_kn=n$eGn5SmUvS_mITzi1uXU-Fo zfV>+7w4XL76!r$TS)_K%*=@LhMUFHIcmWllcw%V=)+ zJ%P|$AUf2YPILsd9p1qK9j+0WvU4)TOtGW@L^A5{VE}U>mg!s%nhxk!N&%-yDu`p@ z#bg-&ewxfqfP8L;MNf-O>0Ge`3)!pZk2BERX+(R!TSdLn`X!&6ZhE+~w>byn1(yBh zl^Y4PihP#68PT*T;=Y!@D*V5_iF*N2HO+n^>x=OTotfh@LZeu_Fon-PdVt=`N3LUY zs2m+sDPQ>aZ89{{e4?4uJ}Kp^3SBpEDL)nqhG|zgPyy7edWlB|x=mZ$W;xos zy#63c=s7{m{iT}Lj*e2Zc^iwMiCm~AW8*s#k5#^<%lu*6fw%AOC)pw{6EJXc2xDcyY={_( zTeqn9Bm!A!><2qU0zX51b?MrDvWIVXw9D^eJK}wH9#3f6O|5qpbm^7gS@Z6E24DD{YSh_|?ka}P8V67Pe2oQhd z$v2-QtmezpL5}-imMip3CwaojZ5z<_DYZQE>$GN;b&GbYeNlM!(F{Yp|2lSckLdO*ZG{`{Pk%aSCbvXYlUKE+^3L)6fPk8HSsKrUG_ zbI822i=7>aovfzI_d?I?a=Zk}E#)R&qz-L5fi>OXK<7ecE3{^}CsqwZUpPwW7zozX z#Pd}hc4zq8<<1FH)%8{!biBPoqD*2wih;FfW4d(I>uO#N)Mgi2U z)~CLVrVwPRyN zetLoeQby;bkHAIjnnTb0gG|t zl|)ZsgL(DCR#xNoZ8zn+fHp$|?qA20xxHIM-vAsU6Y#!hEa-sAs2uw5-(o z)(;4j9yofZ_F2$zm9?uyDJg3g#?{pcMW(;Y)V4DLG^48?(rUh%G-o)>+rCy=k}(H<5PGV-$JFYC1Iu6Sg$=I!A0;^mML@lPN{nvPBMRQk z$;2|(3CL0p=U`UcU7HaCELMD)a1AGbllfBRL4FZDUy+OqJnpb4tw9FcOjktAA>4~C z2T&`l=U-cGks0cA?01>zLr%%)%H4M`e~)9Y7xL=28I}|~PeYU5NJ5T8$P=fo%ALqv zpu2BsE3Dtj3Q8Cn4y#SXmK88*{CFELC8_b0_cy@iQr^ho~LcOdO+ zV<`Tor>SHC%;~47sHlgZ{+?272Iafu_Bu1_sQ|9xe;r5>v?Ck;zW4t>g^}3X|5~H& z(*7p_qV=yDyW+*PS7tmI+8W$EF-N2x{=oe^NN@O~Z_}}hap*98^!JrNr^ZN2rl~=^ z{#DN~N}<72uWj#W(jfYYA)FkmYJb(YV!;mEr@QDdyNr>ok_6U6ng5;5#bcsTAlCd} zZ4MPBbQ(_-J}eth8DIC{KM|CHtw6xic!MNtj98Z{GMT;cD;|t$)FQU-(ce9)5~tdF za8u=@;wN^fMgT#^Lc%FutA}Rg7UOfnUCX~X<_yVEeqB$MAM#h@wL~ANp+AqaqQ#o< zQ;sh+a{5SwSO)Zg)z=euc6Bk?2gkY@*m4BTLPr3HLwjnl~-Y9O_7kgKk= zcEzw0pjt_CC|OAw+&re6)3-3NWtUlvusrfC^(*~55Y25MaOhNQiaIt_%y@#d7Q498 z@6iJmTVut4&!{`QqkE*Sea3`(%9C@9e(No=LBaHxj1MCbMwOtEWG`d+{y)P%Lp_Dw zgJb3$*1(yoNbX|Z(#m)~+T=fq#mH6bmi}jGucXAHBqQXrG1^X|v@I&vmx~2EOV=PH zj5T6t+t$*VSS_z;xY#pBLrnN>Mk^vjp}Ay ztE#C|i^7=oFL$DwOFsaW+6;aDm6gyRHHapU!Jq+;&0HvG*Rd$4@N0UE;p``ueu$nb z=u*ECFUmm6z4f-~E-engx{G zEz$~v7K$(~lz6-n(jYBZTd45-?vIGEgY>&4+BB@?En6eSuc<5o$DnMYP_8f6SdpI- z>^|$NOvU=$nbi5J3}>|OWA<%HATSq-(;Cpt`#6T05`9Q4Hd7DX{UrhFPavEhMs-Eb z+FMN)e{p`qBaJ$1ki5Lq+2?%ygy_*DX8k93X}eP@C3o=Q?1kFHWzr8J-OPJL#{1dI zVI<~bOguJ`Qia0?1C{zE{Pw7|eUTWP0;$x=@jHP$45z%R85VEvdW(1)C;ya|lcyg^ zSS`6(N{4HdQ;b!g&3XUrru)-;x9v!Nu68d&-JDU_-KoXUX5i|=QJUXg8Lgm7z@vx= zNsSe1hcV6>dUUOyN{XTRXI?8SiUm)rdwL4lHV6VCT7w2WrnwrM-s>nEIsa&)@GFo% z+QB9=P{%C%f0|zWd@|v(4J8FsisO0XM?xx%Tzlu-FpFPhnv3PW@q|e{Ax|bOjF6{P zd80oJF(yCVw>;?=&mA%9@2aP5D&NxkhhU`?OzI3Z=~1$DYHe*THgEbvuwG04KLo1< z_m7cmDT2T`7n(3q)|G{N^c?R!LW3Ui0LaIB1pd5a#$A`O0;W=8%~L^kYX<{itHoml z(;s!aVMQ^_#qN9<)s`|mz9WI$%z1j#Cu^YS<+2)1p=r1VaHspjse1((w&U?J^aqO_ z>0M#aFBnIrBUx^auabta?Vm)-F4)RW!88i zDw+<;I(n}Y?jzD0=P@JEgN?WDXTK#}UHO0(1eD}4NlER!TAYb|n}Si+En?#53y6|G zCE?aq7$_Kf;tE-`%knyn;*F7?kQejG*$1bGhNF2wf8gxI#Kbp}l@7MN?`LYAevc-C z9)tIle~SLaGk!;*qH<;5k0pEI^cBu5sHm6^aWJN)rhowarS&hiUcKUPZ>F(nTXTL) z6iC45yxz)5XDb`LG*j^4c~J0>>|Jc50qFaAne52##***4D4C5JKzf6T4m{?eU>$_g zaBDow?kwa)c`B63(7O5pAVUu!5EE_OM`V<}jj#Pccc)dzbnh4c)-W;Cr{GLvr;T?( zLy^k=kAS494x1Utk1ZvClj=oK03l4sk&9qb+T>(OvTO zngV@_I>sDvof5B_lG|6(xN8xz^@O(`Y*@G6q!EUmICr6a7?uOhS*meX#>?NnJ^L=7 z8rbIGD8;r5HN&b;IOjikBo*JYzEx}6V5anzTL8NK3MgS#@EBTI4S;rTv`8=PkLJ#7 zfcl3zOa{nuE{vO9fv805cp{W}SJZgl;7_OE4La6a1qB8^#^BD*w-C=o40%e?MSOb^ zcPdWr#6Ps^Tc95m@y69I3BZc+3KUs#r3Q zi{17l`#4-bN*$IX9YbGtc-RB#uPV!;+;=x=YkkQvU%$4_d3g-wjj4jJMAN<`b})du z%fv2%AlN6UuZx3T)8YijMA0(>2iEUm(<#DUp%;#~2Cd@R8?&_pD)hIiE717i4?x>c zi)mH!ZNSnVgZ%0)Osj?10D%alGDwxjx_~~SD6i0*a4)QZ*5dkpQ2Fj~Ihrqq%CXvN z?|!nY@?+gbR|RXzhXP%;W? z*4iAUT=D&<1)vh&quGRbOKp){aX90M2i<#r#Xtnq#BU@eqvN2VJXAHVluuc>{Ife^ zW<^Q=87@t8%|8+a&@T&N-&oatSD;<~34%c0ry$X;``)^q+k=gRUsGhsGC8l4-z;>RS7po4AfFtr?Y!vseL*u_;ruhYM}^~)&6m^5yoasX zPu&bQUhx#__Z_r~Q~s)qAy@HXaZ0zXyF{B_zO|?F*~9m~I+b;E#J0zkefxGFG^*{@ zi{b;q4dH&0_t0)tB-xRa=CQ`iFa(W{TJOSPFpqE^Hha^MJ;TjVij? z`d1DPGMW20gV#m7CiZ6 z^8^_OnJKH{QY+A$I+?n;1^;qFcgzI*%ZUm8AkL{L;`R%z0elXzxEe|3MWYoa9&C?p z9AYcADy(K*dfxTC>nlvmsd9*WdiDyH7*%3zYU`}yOZFWm#=F1QFI>+V>(?)fwkYw} zy^3NJHiQ)n6n%MT)Ztp_sqpobkFP=BdJrj-ezA|%7m`WbmDM$l_cLVea<0i55HECJ zysdF!95_*X1T7oySrR0Eeutt!=M_;=<6XvB98W<;rGV#l7q#pRRb5)ft+6}L;lt1G zrZ32Pv$CE(w5*2c*wbu#s{Sw(pB+&UZUg%I1VI;SRMZ=Q+#Ow*rE^%F3vd4_P0!Q* zQYT>XFo=L=Ek;4K4KxXjuwbrNv){obVR`)WrSIj*7N#k=wfP|YRcaj@jn_3%koClY&Z*C zU0wHk?epJylezDrPhSOug>HN74`yc3yphqWZ(M8%3Y&#wOFlyZL>FSC{=tI>D7~qI zQ4gLEJkavm3~9N9Qm`b2f~Sn%|0;u5 zm8+22Zf%3ksp_+v2OD?-rtfa&1kt?|?Z!A#zi6Uun}}J(B#`ZH5b3{PRLeXq!1g#b zB2|MZF4gv6HSAKTvZvV>y6J0S*XaZmW(cyUB?dbZ>Ylg#f0Vs-R8?)%_6sN=AcCZT zfHczGp{S^UbhmVO#|A~DLApUex;ruObd7kGz-}lEk<9uff8AG;!ti9H{ z*SzO-UB7wx>(uiSSrr(e_O-lXb9S}f{rnYSmn3+Fb!K&a8>ZBobSOL^eEpP)+$%vt zy;=-FEjr2bFE%6Z3HUn(U`%?0zrXP%_v_Vj?wq=5Lk#hPVu}zJuj9)n>s%iJrNP%8 z(P?EJX5wzXG=Az;Wt}FMu}RtKw%nBsEsrtdA|`V`k-Jl^4D3wt*w@UxFN3GTOvejy zondM3;FR3l(HDsC8fMl9awRk;iY|yxeAVTCU`Mz5>c3}=N==1dV&lGW8S;Gh-6!s7 zo_e~<^c^r}vAB|jh=Xf}vC5HynfW%ahmb8k5JnEw4(nN4PPB%BQ|)7J^t2_e zZumxq#?coS44L0G^P_cdqx~i|ccMLiD$_F*`Ix&FLbvTwiZK$$f~O29fG`*|BsQ-$ za2(;xgAp`TPWrt>tuKdroAO%H8;U|vMcK;eT^CB`oKAAh&bcVLh>^1Hx?x3VEW*1| zQB#_!hUO{fM>}4wDLpU5`Dbd7Yb~oW`QcF-*z#S1$Y24ZJtHJ_rqOY1Bv2WGljTcI^R1a~L+YddK#uYt#ia+Q<;!NCS=-O;}4 zS?2)b0OZlFDX4jX5f{FgrpDbJA|a*+G8~nVQ~#>E!eZ(>@QOli9g%^=rD~}xmiGgY zF@B2~TJn%UAP~i{Cse1iR~5)ej{>#dnfB zzRpT~@QM9Wz5{cf1Ua&FcE;@XzJ)Xyv%db&GgVfGGvkHxsm6Um3x5@vIm!m_3_o-}+Nzl4R%w<(Xe})r zejCyOEM+n-6;pdpUwRP2ZmS}mkxp~Ek24Y6awLv3>q$5;i)ZA<{lW~yFw$&v-+YAw z(UD$m7N({l(>*VOK_wq+{WW0lV2Yj5+C^2Xo(U0K;)mS2og!?G5Bfhx z_@P|L08&FSPn`;!oVd%*+)g_Pm5*PUZgu%z!r!_H?xtYZ-OGt$)nV{L=sKhCoBcwiyc zQsSrh@#(Wfvqjlpp@xh#hIuK46kM{}N|-&7X}+{1cJ1t`aL=cKnz~<}R$hKR+I0i_ z#E!Y|tW94|?xslNe@CD|E|f;c&Ao(%3HCvYUkOS%J95Kl9~B^_x`-e=nx% z6N0D?d*Xm!`)I|}6t{gE^$M$j3eypgtzajIuw@#+RW`)d+qrTG+h?r5yrQR%18z3< ztz_p57;mS|k%3uhb9F^1sU(u_UY+C3LZW`)jYe{bG9>p$&X4r;^cS}GbG}tyJj{gv zyaa4WA|n%Zb|JKp$l$DunXO%P9j};hn{hjz!FB%J=yY`!Le&re7A<_fkP*e_8*AeQD>>xI_uOaEyg0PQq!+e*EhQzf1iZ2De|*G?A5wfG=bD*q z%m$fNMZtX81Ry-Qpk4staEA#O(2)q?NQv>l5kVF3{v)qJUmpHT5s$|{&2PZN`)_wQ z1J2@n$_Wdo7XHX(m{eJ5_B7sJ59M%k0b{!Y`^t)>#+_XjAm@Vn0`L8C+zU+>!k(B^@@MkHfa-N< zO&1YXYO#fL)p5S^?Kzemc;AOdgvxRJKEB7Et9g#l6T`Cf{-_j(eBx{6H|3Ag%CsOQ zOuZVTLErbjMVj!Xh{Lb)cw-yBJsRyt1aAtD-!oQ^s1-S$cjiSuevC4dEyJqb#gATb~rDvKY>NQ`$WX+%9ID3f25w+ zUSn`q01{B%2SuuM^~Y4=A{4A96bxw~oUO}|SOZmI1rFta*006`^H7hgE#ra}~N_sriPq12H#qLT3zpn}AlyajKTZLgS-@f-QAcwVwIqnG$oFg~fgaj=QCh2uZktbmq-ty0JJi2jA`1%KZ1mngF-eB_ zix`a}vprm;m}r1T@vD4Pd~$|Zla{xNOC~>0xV<|UO*zkV39aNV%oMG6#I+tX@w~Rj z6D@f-?rJJFCyL=G7I}w)jBo&8JF&Behh;-rVqiknIJw!qGvCF6bLjfI7u3Ix?aO=Ki)hf`(o({SXzyBUbdTObVgs* z%v8bC?|*-77a6b9>ZF``Mn}tj)A+nS#(I+aEU#NHljPCG0V>zJ_vbnf zx1ue3eD;W1qXTLgiA!9UdkgK}=$CT!K}vAy1C7V?K|~ z-{+i(eOAo2JNXiYV#U0RHq$-O)5^hx-}bol$SBB*JLKN%tktr=nas0CM&fz)7(q;9 z>*YjrzpYkRe>Wu^T~Je#F5ISh^-l(qR$axc(Ff%Vl>mCK>0)WXTubTd*s@EL)tpjo zipWtOON}%&?!FBulq+P&9v)Df)=^LF3+-`ijEI&%W{FMQ9=YwW&O>Ts_Bim8>X{1D zntYZF7g(QZL8au1Q;wIt5L--c+)SkB7sbRMUrI{|J`9!S5^YmaK=Hj7Xk+reHdX4+k z2jY(L8CA3#F7w!}gO|JLQgttn)`X7Q%H_YU{h4EChMEsH-c9F&46N4t^$+ZcAJXt3 z^*yyPK{mc0In&%|7+4u*s(gs*%(cpl#vH?4p9WsNzT`9J9B^sP@3W})_$5y-x8pIG zQ^}X)WU-!)VR84ZdDXyBi_npTm`qx6Z+f)I9+I`Os{fjrhEAu<{SbzXf*NZgtm{4K zZ}w7BDvDro$JaLdQRDYKD#y6|~pYKd6N{4@VmQ7l}H@u>ak4^)MO|uYU^62ct zP75Bbk(i0@MF%$laDDVN)E8sakU)BGcktb^uHWJG^fB3h~rTt`9@8u-IMcr`S@PG(2e^No9;co>g6@uAf=C~ zg8B994-8q~nYF5tr~(2u9u-Lbb0R`p&8P;( zpJU;m6SN`8wDQG9k`UPn2a26-Aw8bNtkuL9`(mJMVM}BpG5z8kdCb|fH2JUo=P%K| zqRDm(J#M)Sn&(e$fZOwVHJY@eH(S;mCN zl1LWF$8z8LK8HK16&t!R*F5`A5N!XcssKq*1dzh+FBx(xHb|4jp598iG!6eTCBQ+; z3q5CeQ+N2^3vC}>XQKZns)}i1{Xsq>9@+BD#QEiEJ)4FsNmZe4Dap*>jNa#{a=qP);P$PkCNFLlZ4O?pPq3&TRrt$tzLO>QM^ij;8s?n$qsRz7rVlEIv5= zr|qWy0dKug(DR@n%B9MrnmHCGaSZS&bY6D*HWT@b_KzY%k*Upp`mx(@`AgjaPKghi zkDP)YLHkvZ3UY*+kJM2N|2;R|-vPX4-wi~i0;~}{7of3yD5H& z(u*~&ddk*NqFVRg?`Y%hel3r@ywJ>j@m7aYAhRhDI zo-~QFUF6*p-P{aqdCKJ&{2WZ&ccfN&g?>?W(0tF%Ea8I$w)S~!G{!K;uYv|F^2#H! zVb-WLS9~-S`rNCYYaKNs6w&lQar7| z>7oQz>kfCr84oy0v@ILAGE(EB=#tNkimq=zuWOj?n)=qG&peASC4V%-LXHFpClyZ-I$*-_pb{&yYNgHnZEkTK!whH*2*8Q^3P_^1 zxWnLG$QLgB^X={1_hUt$Rb5^vwYH!QnsrsG^GAkDi@TwKoeF=VD+)Q3I57-AaY}iY zea?zdT99u~8V{YsEE&rUis&~I+GkPAzB8oqF>D_?xWygIoEV)Mo{8d&e_m7c9=N*()Y#SFins(&}1NPoMh&SmnVZ|NQCRHG*lKFn>puRc?E7H&PqDNyM$%a#t6t+bVMzC# z6>&QDi`|iR_+pCB^ygo+N6LsaAFcJcf^9m6C5b2mX^bj=1g7Jar;ORWWMgx(DJOD$ zgF(P@d0N&9IV$z@htI?UztPghh93ZJj^=V1-3uk=rh2Mfw0M8T{*z ziNQ=244^Db56P7!BXO}FV4*ho*QsXa*R4drSV zDri~Kh3Y3;BN*kmoEU`ttkpYF2pZpR9{F?)3=|IDU43)Da_eZIB@hj%yv-qTrp(b3 znhVl$>0nr8b|Q@Tot%`F85?3iLcNSQ^=vOrt^C>tPwMMVzi&!YQ4)V*yHa<(Uk~KP zXYS|whUfcpiuu|hSzi(kny(dX_+B#Io}NqYi+W75J+LppX0+??-&aCR!Etdfv>{tG zq1i9bfAZ=0q>vj!WI$Bu(-+}(5C9z9s;DpyS-OJmu2;wgqMEMnfu%}3#ogLSoNxH+M{6e6h-QCZ6aYf$5bEefc3Rnh{29@ZKk-<>lE=81vWlL`fV9KGnbzrz%CrEM~D(IH=&YnH!b$s(`lA#PX=?6&T| z=go$3FLkrH3C&P&u0Pf}u|>CAe?-$;GE;3uO-cFEyDyQK(_|BR0)?WoHXOV>1=z|$ zK+1{dbLEJ>mr7{M<0dJ}-?-YM6X&qllofEUhYsuP6icoH`v=Awd3l^_r+pS!!E(pl z;&=(7btqAfGpCVj_3ilxR_$@WpzG4*1`CO9w)E|fwv5in%8eWHO;a6DX~35bfD^(o zOF$S-9Gq1?w-fs6g(32cE>3=J#llZ9OzN_$HJQvhzX6H}B-6aJQ@t_=&x}(%e#}_a zM_2BQ+n#%3xmGVs-w_x=M3d|Nu#CG=71$Gcf%oE9OiaI}mK&acIQU5s^3rZzET;Sv z@7I(voJb#f5sGHSpnGJH!ZM4@L(P@sKWL*4lJ^{HeBmAi28i$S5q$%je!l{MTozjw zC|etk$%{Qw+_&aS)84FZ<{_PgjRKm$62R1EKc}k!B$EweJr(;qVe|Kv!;GN>pk1kdbJ}X8( zBB&Sc*=Sh$INf%mSC2-*TRYg9JGfxf)G(~MY_3*sMf#9$13L~RHrFF+fH#Sxg|>E$ zDyCU?HCAOnoXm2D&nwpc)=;Z=O0jA<-e5iWDd+tsDiYX+ zz6rt`$_LpcG#Z<_fKL>J2H`XAk4k92V)~%A-J7RIjuz{re!IOsYTb2>>ofZ99xZ?Ms!6IcDfySng)cy{eT{{bfZ%e7gQx=kRoji{{G_d;M4NTHM{3@b$bls z5s^avqv{dPuOqOIh8RWFU!roA=59y@VR>zc<9kj^Hq%394m5S+!+9FT&k_0(xM|qf zUX)LD|N8pXgbU^S_wT?Zf&8jWS<9}>Ii|-xd)y_ai zKYou;$;Z*@E3#&_hA(Je;Rw zK5Io709rER8n{y>rsiqC1iku0^8_9K#|yxur=OT1pZn>{7sKsQnTyNV4+tisW+L~i z@2u+gly7ct!IE!$yv{6id?PuJ6@%Le`|RPNlc$*by>;n9J?w%vcD7=L001{niuE6P z9K@PRePc)*dol~ z664Vqvv&Mnm%ajh6Z!g@er&QN+C>x;qT0nQv2Mp>rjzCFbUWh(85-~z{t{`FUipetw} zI_?4S8nFKXvip4oiX*?x#b&gZ#%LXPJ}!>STC~a&46K#IPO=rB^M#B#OD(7&XfHOW zlJIi4o!qC5Z9N!OUT~X5@qU8voO;$d!~11K`}6?Fw!&&G@EQ&a1NlnwcW~EV+H>^> zgYT%~1g*(>V()>3I6dn&Q)sbJ3hY7E4IcHXCoKrfF6L|NVD0lp37 zrdoQ*A*&7@reqM8$6>-(^rH7dVQqi#5Zuo9`|_0XT@N#3O=okS%LpCviT2*4?9Ehj zZjVjmC^LWGNOcdD%g{FZTXKzXaTFv|vsa}7x>AB?yK|BYf>#Hd8DYN?61r7JjQ+A_ z*5qpPiJypFUS1-AKC-sM0?;F}8h8Uh22?gvB3d%#W5$qf`Bb1~vXrDGZ2M>)_Bv}B z{;_ys#jf^Zea4!#4!+Ku!g|)x$Q=Njn;gq&&)aycrH|Lyg*fb}%Z=}Q1r`o)@$jIa%aD@Q9m*4&DDM5yB({BVWbz#tE z`79yz$IyZa0K;g;RGn}IK6@8ZQ}D=GH;Fc-=B0g2k`{FQ(!X=$tUt4XmJgZZrkNi| z(WBWT9S|tJ;zR!ZicQ?lvVhb*5PyaUdD=~7!qK_=n*3lmh_%FHO5O>6=a(rNdwA-c znTkiv$r<^aD)r#5K$iZ+@p@vg$ANa^?cGO&va&MtkOx!(3kd0ab}@OfmCd;|ltBkx zS-$W1@>qAP7>0}eqDkxBK4sMd`4$O?Xshe1Wp-og2-es_5DrYg)^8(QCsk~OK&)8a zzb0?obe$+zXiGTVm?%`F#|J=(Z_oB-+#XpN8PQ@1Ur{4q#NIq`gmSf^0_R~acr&%i zz#msHUoQRy0qN1r=4>r41|hTF(KWd{JR8JhK%J`rjH6o@*QlCY2Z#p7It|wc!CMQ% zAbP;T!Etdqfe%7dx|M2Zxi~G0&Ay$qQx-fxM#~|4F1}rRYKrj~1rPdjI0p7I=>OJi z|GLZBuCr?Q-?aCbj#DQXr?`Cw-SeYfP8o2?xSadsKD5SjJjAH-9{q}qbi6$OWzSsE z{I;1!q;X`8_Dw!C&%9yycFszrc1)vVYvd#Z{;|j2{%3{hMC0HJvX)aUvz8sW6uy7Y zce{@PCYa+Ue^ek0f;kQN)lS>XmdSH95a#oFQ6wOTfkSbhK*T~XQqSVVVO*Q2UIyFp zL#zW(qS~xGbU51f6L+o~b+7tFV5cEzj1J7Th?pFQsLY#Kxy+ z*N6}@YpeA1q3Z%5dbJ0Jce?|9^kZ9oOLsI2jWJ{ zcVrBg#c$VBM;q$%w*wbAS{s@D7za(zR#m7S5$f4K_10fV3gwLN#>;5hnptlkU-+(Nz;E@rF0V2XP zKc63!J3({BMh1^dOZy(4TL-_s27BC?&cERNc4#fLzF3PU%;NCCEWbLh&-kkkYTNql zqgaJOrK^6(Vdk{;K%ecLyuta#LbFQR>)74ZxOt@!KG zk}6Y-xd@7KeD)iKu@U(MSoH*E>WGB$|(e*^)RxJztJ_fCG*))qtR{C#z@m!{x{B*u^`{pxH zPy1fnJ({d@WCR+d;VC(6DBnF+a(*GReZ zyy%x~_=&f{U@%^g>asqy(Qu#Qa`d`GChisN*K!tk2DkfP!&xdUmo-*EbZ&R1IyGtb zM(><6o~dTesz8~sQziw@ogtTBTkdsFxpsfmGqTNpQq&^p}MYqR5Vl0-ThkSw(Z3HyI-%Im^N_|oQVNtBdAwgQ8E z%u^}01^1Voy)b7r%Hs>L#P$%TLRt#IJ>U+i_0z`}_T?`JfEdLpa{D7`7qC8VF!pqm z|6m+`0rYjyVu!0Ljkw`{YrPGOLfg+?JW;_DTshb(mU{f`$b-rlbmfcde-09!(r;^d zXiYJmo;q{7UPi~}5^ry{0KN^|ixc1O(SSgPGM(l>)W}!oI~YwvZ4n{)TK5~YfU4Q2 zpFlW04;+XT}%KO@)5CN9V_`R2M$bIeWU0HdZLX^Q1j`E!@*XG*~lCC;8* z*WH0Ye(^<|-B@?9TYEcxzOk?yEJ&KN=B$Uex(hvGF989RjF0D5yOX7ntE+fOj~=~X zvq69~HWp~q_aA2^Mo0lQmFRT2d{@-Qnlrsdyzj~tv|I- z?Nfk%0Q5$-#tTVOJuX;a_oe7Q+A{gd%=|V7vf*p*f7A?Ff>{HRQ*C=tHvn^!eECe! z{cIm3ZAA6k$U2yh? z9)gq2`D7p?lg?BhZ|`KNonBk3C^b?)Y;ihZmjc4>XRmm82*4<#F*6!w=8)5!0q1D& zp#L^rs4-M#C}hFU+e zXf#^+T#bh2_0;{~HiR&P-Tc89s$(fyz}mzIB$(br;ld(_t;poC@VAlL>zjhsEa>Iq zxs~{w*9XX?ceDdX0uRd3opFm+uX`M==8`avrW&ta{TwJU`#?2?Dv%6J!Y#nm>0;fD z2lq?=3~%UJb*4RBjg(njS$PKe2$d3t;|898t` zgc=FBEc%O0-zN8-feLG~`ri9SFOXz-1}?R<=bVLi9VeZ=GDZ0DaC*}=^IpHvlkgT5 zlku!{d@r!*-h+=lVCg)BzRIJOwp2ywPX62_?GTra6SBN28AEjG(&gqJ_0T0a5(xJQ zHk6DVJgOb5RpR-vH>{!h|L+{cBqKv#w?LQ*7a3XNVhHsTk&9|rkgDDvv zGZg_upFwlsx7{`W)2 z6tH97NIMmsAA(H|=sUgNs40s3LICc?%d(vglm{)G*5ZF@;fY%P9#qp#zzg7_E{UP8 zhk?BK&d3hu>Emaj_eB|ja`N0LO`Is>hDTDfKo0>n@Tfo_8u$af$u+iwh{HiOASU?v z+QyV|3Io?xRDDAuKB1UqbnnO(lTKaSnj|53Gxq14xL+!hIAs!7s<+1!&Xi2<#_mZc zg(r9ZD~9-OaOTXZZy#*iOfy!To{Awem%fSjod|49H63ZB6cI@Q28jos#BKqTon0B2 zu}4ckS>7veH!K&!qNQ$i&V+bIyV30f>wK)XvZbaY(*-ir)B26Ku6hiAmyzxLE$3fGOMH-!2vqd1 zu`0N^b>aU7_%&o_dZtsI3NbBeN?CNpn$3kMQtpRyjiPMfk zkq&FC28RRvTENi|+aoOLjqeML8oE_MO-m9;D^ZhYrlr}cL67H7u%tkbQ-t?PPem$OL8`nDA4FltH#+{qkkbb?!Q9HgpkTxYd2i`!rD|%UTAQcO>jud6h z9GtEL(*nl&03CHr_nPR2h6T{1WLYL`1caedb+oZD!QY^0nr2T6&V7HQyi*_t5>+~v zOGnE(9be$%DWvTu+>mPX!A{pYOu}AykFmF2 z4Ea%%HB*AW;j8poo>#O@&&$iMX>$Wr^oo!BPrj{DO-b@$aQK*_qx*QMFmGBUe>3O$ zsYD&-Khb9YM8AJr4v~S_Dj$#v=-X0^=x!egcFS^Kjhg{4D1%ghNS1CoElvMZ!b`N2 zOgrkDe?)qJk^r%^wmqlMA2fX)Fh_9 z{n_tvKLzAxj9)G~#6a#|qLuJc#P%Zn`z7z68Zq?|W4w1*yR0#5{z2tx^fP^#yJ zy6?pIoqt2l>1BSZ3XM#3s;1>3ip_m*|6jcbQ~#S6p-6%E=4F{xeDSCMGU>n<;f7AA zoQ*_YD4t^apDW@Qf<{1Xblex(6O{P3Qvd5K?ZhLAlB{lg9%a(}&xFk)ecn52X)D4h z{!3uVzqm$D$B1}a`tLD4u@$zRL=#ZSYEp9*M3-)J@1yAy|Ftu8gSinCzdsh%meQqG&RC>$ zInA-4m8fj{!5ja8$l!dcf`ZIYndQfQQh{Gsq8_QD_p^V0e?3>3-bv-#$COSMXy4HJ z!dfBB#)b_B%;?JM`f6#sB^5}P*7n?Ugv1HMChfN9a4vU>VOxT_CYfH)-fL3+ss7PyT zT@8$kv{fdA4gTtm7ip;&TywHEc0^?MTv(3Pp~J+%q)AEE#-^5bScEl)&O~u!Ut+Z` zR|3N3JT&oU`QL-iUp61q=TZkLa^4fqIxQ9^S`wD*?OEYit8^`p(B=RnQ%F+zVqM+( z(z`3%moGhrIvL-&AcHLa11}IO`^K*`1+rBKN!FYsy`D^)I|}-5AVr{hMzcwGt?B-R zRrT@?$&<{dB^;8nM1fB*zFtkFmRnCK%SU3_?haZh7i+da-=Pw`Cc#_m&W zaQ`B5rRA&3+w%qX#J*YEPSTT|k&O4(QGcGXH1v;`ez~;0ihVg>9U1xAZ?asU6E6J{ zqiKg#v}Yl4!j5C>)yFgV>O}jo$p}Z*hY%P#`!G1d zctHC>U+Cw*FYK9X^^^OD<~r8{G&Ht&DG&<|&n1V#nIogYNE=~y1U^7D16Ik7Kzvx~ zPO4Y;t~w%U8sXMCZ1sNgL7gi33dW&@fDO}eyRm|#=11kk5FJQ-n0len2}yCZNovGs zv#-d#1p`RA>UH*$TFg~NdwU~@gTgR=3ed`Edq7UOx8dL9>x$}Ixg0%j<($Fcj{CoZiT8E^)y}h!St_VPMp4*t4 zFP6aqzo0^j=elX*jT0>b{MPs-u!tA{F=dv2H6@7b82TDsZnTFYoz#ucH1OMaC#&)S zA3n`Z`AHO=(I)QVK)aFGKUwOtkGLwa0C|C#{4yz!AKRUcXqU0>$E7?WWNTN zi!PW45i4A*oUyb;sGXtTIg_JN%|hOI&BF@iS&#VVtmEO}v?M=5DTfcrA!xhaeHIUK z^95Dk!Xm+Aj%g`W;f@A!J@6O)|-#`|0E2I2t5$ zK1_1-{U6FkO{mE!F_#%L!`N6SIdDp~ns!>1>k#&QpEvn~TonwOvt-pXeH%JA#e-)w zu)AhJJVL|7TpRM+M_wG)5L|SsceU=mjul$RD&KJTA$il-)jO2YDa#PeWjTD2plSYX zZ^qf>9oZWvE0ODSa)44EtWyfYNv>YR)CaxjMH@aMP|a!yRxyqUOg4J@@iu%nF34+L zYSSxa+b{g=+fBx2gY6A=@h;Vl`=ieRes0{-%XXs41rGa8um@iM?<6h@pclP;i@537 zm=lGbB;EPpUd__TuH62wK%(Q2*VGjMi!VH&*8(b79UUD-vqHraZffQdLuw|b;D!bd zh|HwyYALWAR#?u&WPK5O`w=vt`itomL|pKmYzU%NZkX!gQ%a)Cn?Y2CfA8 zKNb)O8G;+M@%@GpJsv`@Mop3+=$fb$YX_5F)0(@TGL{SQL6%*@EqM~7fepCK{uslO zHSSOLY+C)Q{7|2L{vxAwe+6~AnG}^IJEARSdKT3Kty0*I{R5rNXF;a z4#HlbaRo=g!_(tT5cK=u>nF#}l!Q1+A<=`k6Y zcu=1uR!-`Jr89|zQCd(n`q`)%GYS+XF^nXCidH7)@#GGm*&i0@D+U9iEdsceg};5y zrV|vrj_5R(%_e|Lxg`Rl%;Ls|%GU|?SB!%BQpI0KcdUUZ$q0y0EkAg53}@TUZEnq9 zokx$pm6kp{H)LLNSQSW4nVl6{?@v_OT`?{$UyJ_78OV?i8XCj=MdoRi0iB^ZATBTz zLV^fYjV<{J+JxF_E=u_TjZ$GU6}Y+i^T%VnI}eiIV>q(q1dit8>2F(205-Sn0f}$w zQPT9`-LCJ~aU<>(r~JVk%#?v7-nahT7p{#CCX+cLr`w~*@AmAa~gCB|9|i@c=zt zyWITr?t9&wp}LMKQM||L*hDbdEz0?vGb_*^0I^WpkyLZf5xB-H)JXweB#N*Sdm{dO z={6y;-2~g+b-OVTeA!luz7p+4urS@vooqGSt7&LhcUT&`BN@eC+ zslO1stSm=}ea8Jl606fkE_9tHZDVw^H0%4&;=pO(dx@Upw8Nd>v$%+m_d$Faz9W;u z9;QGi6A>M)v3ZE$)u61^?^Tly1|v>UY1F#1Sj)txbejl10=hs-J; z`sZNwI&cz!iP5Ihf6xLx;uK#@zWe!f{bY-CVzqX%!4-obXycJFphE!DMg(CF_II*5 zcYG3U)+mqN7hRp9L(ucRir|pUkLTJoNd1d^98-)I(V`>KO65@doBE#1)uV_r#HwIw>Vh=el@6i9Hho{Q zr6ve8AjGmCWS1`8uEG$&^Em{dCl7i@WC+koASlA?_FlVRXcf{H$H8+IFvNqBu6t{( z3d6^!dT!KvW+rW|fek}u)=qLqjJYJU(5I}->J~-DoAixbwk3~}(pY@dfAyy6zF>_xH%IYj>GgbqN= zVIkg?N#X1I&t;TybZ_>&F&D{VB`ahK=6sJY3aKRK${8s0wGXsdXfWioh8E+d z^iLPC@FC_9%O|8V>Ls}=iaYvexvW4GiSG6>9 z=5~g$I81-h8}WyqpF*`0^~cBouSZb*&S%6t!L(o?2CumAfS^K__7Jy9q52p}=g1HOE@k(3XM_sb*yIa@0B*-zT|s;MBOCwxZ#dQL z_^Ffi`8qk|B0N@yTx0V>xNg29Ote=Q-stHd$a|GGXi*M#cxWNDx1jjmF4rD@Cv6Z8ZErXaw->{P9M+d;cl9rug5|d$@c8r3Lg1@ zWUzgR2YhjiBlcBR3y45M9w_&MTI5%}UTCpqgc!E9Vhz=KqTXGcR&zjOcJzpnzrFK2 zn2*BR8qJg}4RhWe_C3$?_}i5Bu6qxVNm^>wV#Yt*ec%;ZAfB{dWFufw^>5bwEg9Pn z=3@Z*IIz2}TwEL%L)Sow1DbApz#Blz#`deGrl`VdjyQXyx%r(Rf{T5xDj0_ zm!oXVevSQ;W5^|PVqbqDslbDo=z)s^u9b()>l$kq_QXFkB-l(Ay(z=yrWTGr-VKQI z#$32aXXBpVmpd;kaWCAGccyM6CB<7(KmXugs8%6f>9534k>j}FtdTdiU01(ZldrT_ zSQlFb(~y*mRf?MA4oqMc3wibSS3obP-8wvs%I}ky7-jRdtyV3|Wy*ka(m)q~WAU*X zi%J0NH6TF3y$%qa>V5n^hWv3nrd4r(PE8pjzOcJ*vWq4c@pxf5T>-`e%0aFzF+r62 z_gfzgmnGkg+WSU#1ZV4HP%k4qI9tjLdsM3|h`<^LOE*tC-Qz-e)__$$)dohqPlB;> zV4hJXfD#;`0_uA7yvD}Xk=s`n#~K#l{!xnxn*$rlrI5tm=i^#s1rlq%ty`4zd^xH% zzRS(0Eeovk&s@$Xav1*^QlZV@7(MoNM?*u7mFq{Pxk~Jl;zg_Ey?LlgX3EH71{Ezs zpcWchW=c7iTD;n>#HUxZ4Cp>#Me@ZPbtd0#E!!pP92$cTS)osxIu^WG3A8(5CmbK2 zkuO){Gcw-JiuP-%TBL_cma1C{-JM{pF}U9t2vCuERP=VL6iqdCbCXDT?(SGiI>D29 zR#3D0=!teyAa{n~V~w$KyzC6M-!b34PJX+@zPi3R?-`3@a!BIGsJh$I?34}g!CGmR za&zu-sUz-B@|Sl6X1F;>QDLsaq1oQn2K+v8A}jTFq&q`$_iYOcsiUJ6dOM1Q$djoX zx7~A4_mOIAa@u=moO8@e72jg#`5PU7fB!{*`3DwuG#$vPSFEPn9w35HfuUaYXDRRi zuH!`3&5>-Z+m+=wms4O zI4@+cJ#rd;&m3gdVH1894i}SB#QTO=$xgz0PozLcKY0uf($Hj`K+2aVSb$L`JtJX= zPV3$-F?HeWrIpOpmt@^l*TgTL32%Fsay)723+CK?Js5iaLN;+Xi8BtJ=`qUHQs#I> zbbR~6S4{LO%DUdOY7!#yh_a?m&^2`Ysh(#c&|`gCTrA@fa?@~=FQSrBBL0-<#e?~M zD$fxNsgS^$C7s40CPq)vSx|l=XTK*J)eAc40sj8Fy{-309!uvZD_FSL*iFCz17N7N zZ4k4{1d2y12S@VHU*f$7H#Dd)LB#23C5#3PsUSu_+{IvhPD=WrGnCYIyHrlwd7-`3$a*zgDQd!PhajNpM+@P*^RM1QFCPl!DRa0higV9eDiOP(fZ@d z`RLXHU*cEPL$W+T?pbZk>rl3y1%W3&y6&wb(d7X>(4RStq*g-f>)h0hd981fy+qh> zz#{3)+1PGW>2_A!OB7I?$o?>AK!eJ9Ev3;m8h8TkZiBB$wg3n=rm#*yMC*NSiu)PS ze%%gBE*Kz7Wj;|zM@jh|0JvR{Ea3C|R?6n%Ma+(=5Ac}Nm0t8(^_3ERzV#0yB|!LaNJ>?jvSMtrCRd+$Ja~QppVZkO>#f87voRx1FM@taayTjS=*BTfkn%smi0wpMyC-r#r26 zqjN8%zdHgXE`MHIY~R1QJ)lpgq(VD5Ztm#`^7jX|EPC9P;Z`#vK%9dCvP&g`eC}sV zUhtWLX-ti7$3`{LzHF6&l#~6rAgl#mqV2rq62pQL!`F@f&S z@=DnJ#(qt+w>w8B)C7b`u658dB|=)i7MBV1 z_VkTO>jSpD-(1^glUiQ2dTquAKmad9x0Y8&x19%9C*+M;G(?=YnRT@ ztJ9!8F9noIjCZ^G`khCx;>Ns+m zySx?jbo`hXgEJc!7xxRW&h^9=KWvP|%qhT-9ixNAx6$&>n8dU4+h>(c8rn>*_;@mj{^F#KqhMy_K zIShYqc#UO$*8>&d@KC9Gwr8uH-{Yvum>v%u+-+*;7{x^-O3UF7^8|q9IY)ufTfF)Q zHa`Am(E9N~!O~yNkm0x!wdPprdreJC8`DrKSPdx|1YKIyyaleW(f^s4T8p z?q@U{>j4`V@YM(CheKFMv?ve7``oHq+eSi3S7XZksSIAQO-&YLff4^0Q+|Su|1lzh z-(F6+riS;R9b72=t84buUBXhWYh^LlwXdTjxXhTV7&JqV{x90zGAhb0eB&LYq*0^= zL@8;MMnXVD5RmRp=^VNQ1SCZP>5}g5mhSHE8oFc7M&JMGb``)GtA6W``P=x zuKW6d4W=UJ7LT5^oF&26G>uVDOWhhQMhry7R#J1CEw*3)H)h=N-!|3V{v4k_I<3&` zPF9o;@BD|T;0hvM7@z^y2uOVcoFDmDuU}uD`SU1x^n-${e4_`QW;YMC!ML(N-O}^w z(ElD}@%i&syg;1KkU{gBt*nYl=b`-PL|c-OSl4~BPkL}XTImP4k2P$eeIWl25$r2P z^J}KlFPl?o?*otKYVGNZ^Rt4=<}9QCZoiyNf}XPQgHH`MMf_K@Qb9^gAKq9Wl$bR= zcoLhxXsgpZ@ZxYbuNpt{ms3F9ti~=jDW}=xgq>n;IX789y(S{T?9OsVdIq?)THT#4 z`hW%H{PFRoe%LU0U|4SC(xNX%f^P$43U+q&(=ZnZI{0jBm6*>Z*ZgTar3GEc_19+# zSI6fOm7uXUAg&9z$E!BT&!@8_qnT@IHrzaBd-Aj%$*8CR(R(2Fk_p668WFOaT>O=Jy35q_R7Bkqgke3xlgxDg#4-4QAXU)?FpEH${a zN-SAvZ|vQ{(@F<%5cidoFJB&jRE(~m6>Vyd!!M6JJoJ~Tv^+jPU0RYdnP!g%h45-C zmm_qd>(!Aub{O(KgBDB#Ko`rs*{JUW-0*HTDTvH?Z`z^EY$6JDi~&CoGO}32j~_Ao z#+KPT5ui*;t3$%X7|)kL1!R&UCwhh$F|s{ztRlK)1E#)Om zj(O6$xEIv*OPleNQbk@bBj_L#OY*P*yHyNa*Q*A`b~Lnc(+J*3NHx(GlDx6K-_j2i zIT=cjRGw8?GSSrWl7m8ZARg_d({>pHuk?bJ=7%@-qT@}qU)u%GsVFI%0rv#Vaq+Wp zDgPMeYm|D2qe!u+8g&%ZBP~4Eg6T(P-b^gqbxtfqS-lFmejm&T$LiHaJE^r&$qswB zpk>Qb)z+g_>NK@2*JE{|&qsPkcdODjQ^s_54XBwv1!*Y%D1zQKKEPm1`QRiO>&x*+ zazhJBec1FiA}l>T-u|HE2WMoP3H$QiUWi(nu2l;&*uf?w^S%IOw~d6nk~tQktkSvF ziwVYko`|0xwJZy0>!$OglS+kB6jGJUhPLX!{+gSM&V37oYDJ<TZ?^VOhlkqII_n!Ya8TBj9jk9gb(Oj&E{oARs)u2 zdsCWQ9h@W~CJCG;YGmyXUZB-Fu2|P}Gg>)L(y^B=A~SYiXfMusOP4Wzam?tYvc2=~4X2n=1`a}@Yv@NyNb zAE>e{$7x7^Wu0dcO zH&|hTsima_GTFa?C_NPak*>@3&hmgjq{+6A#_SnMFw{Dm+b08+Z-mNY6>|eD`8++ zsFc>Y%SMLH2ldA)odOLDoM>o8YpEky#BUC?pnK9fXE$wcP6n>4t%N+GG`q(0E$Qr5 z^TCf=QI00GYfYw7d9-EVp+5d$N6mbAF|Qp3_@KA$UHV?vaho&KZ3l;$+Tl@KTH#Y* z0yub;Qpa)W4&3|B%ZKkb=T?}b3hxp|%j_SY-Sv5kymtoovMutN(-MS=JolU1x-CZ; z3h3^|4<3x=t&viOTi90X60qT2s(hYs&)%4-pGg1*?64gqcF{+QzOQIDsvId)KbNc2 z?z#s(bmolZ^4L{eJUD+JmM-3=kalrl`1FIs(GBcA13*~8DjXlU62udD%id)xymGzM zgAm-+1jD6cg?@;geh~2Z1}3PlT}DONO((+>YN2)q2Sex-h&nyv7KsOH2FFnrtN6b< z8Cbu^#Jrp1b%0D3Is0dRJQ_+X1KDRi0%0X*p9#qVd)D;&t3Sjj-JI1L^Zx;dbG2os zVf47Sv$thp!e5KOro?ScZQ%50`K`n~P~#mzTcMup4SjY1$!bS6#(lm*>Vqc|)CIQE ziDMN zG#BlXs9v!zTsUH54B~ut2NR_{&5gQ_I1DWtBmQGS*t{rG&CeQB%k$&vn8h+JCx|Ow z&R>44Bl$f#DGWG`*U+q@ubeVx($LV*C>eIxTm3N&rmIz{HSxJoehLtDs&;cR-|laH zaB{-)xN>GjyMA0Kf(l z6eOlg_E>m6MEL0= zTXc+!RBHzGDa{ThBQ>Xrd!=^g3v`k6C|H|_LIaiwfc;#xtB;?b`>Rt)w9FKxDey<8 zHE+V^Y0ayDr{TSeq>8dC9zdJGjR+5SQAQ`fiS&r}SrY|I`ZSrACO*#A@B?-9AL-%F z$As203j%JeYf?a@1l%oixET(dNPsA}Z=3kV73igVG*QTc=8AJL(XV+wbV`%)9e_~q zXy@Q>YUkH9u^d<%Aro3H=A6eH+I)e*0aeTHpcbdvphwML(_uT2knllf;2F>5rXfYH ziRM_gW)Og3qz#!A0Y1faLQ`|=`z`#L>d`$Rzc6qrf-n9j!a1t`WrtJ^eZa{3XAcq% zAdytlkzCr_V~TwC?^)aluS4{XO9`*~ZuBSrW6`nbe8v0U&y@dL!U*nTo&TEz64Tf? z{l~O^kF9*j>zlp$d#DR*{`)YE_q$wDUW#;R2j>WpUv9o=lwjl^ZU0qd!M#>Xx$!8Yfx<24^6&|<+X9vgC3{p)>rxRfzL%Bmi$5*$U!S@)nyL;8PB zFj)c#CKX?0=?gWJHny?*|J%!4k=+>T4Tl-d4V*z__5JD(c0(4`kb2CBfBv_}qa9yV z)+vqKMM7MC?@I;gJoDjCP#zL3 zhg2ZaVd3w?cG#=wG&b%kIab`F4d6=>_H!q{lw;B({(i4NeRvw0*ylKlbcUUYbGofY zKqP@Gj1B)2YnD;xipcoiRVqtT&+SbP#sUB$=a%{o@ zk5RD6u$x+{OBp!o#NNFh$#cf|^1v-95^dBB&3rW6D1>AfJcLalVX)D!31$youl}m< zrWMOYe6#t*E|%YLbC1V|sOqTm)j85-RqzJRR_>`uix0a#pcWJ57LZdljenBxNo`>& zg!DhTz@OxWzCQ|Ol5$9u9Uxi@1ek||s2yaNS`aO~QZh`GQZ zZgcG3dsTW;p~7rBVN$Ru%;j`vWoJ*afi0;_KC`p0i{b3TuTeXH5T(+3!QE$&)E><1 zOBpD=Z%U`Yi1*v3_3O#r3~6aLMi2qhv(rBx(2ACio)Qp-)=NKGLeI;@-)L{FVSZSI zZacw~4#JPP^6tA=!Plp*fLB=hC`fbsx*Fq3^t75U5_^z`+mhy-tK`dR(s`xGAn9JG zzF!M!!7V9>sPy(4_2c~U%=El98cXE?5@_Yza1lTCVjsbQSbNzjgrG!zkO>>j=HnUe_T30j@#o=7G5NWIiQZ~ zy=p3H(8J@lP-~2(k(#v%1&~>__Z2H~@auR`UX8+34(gJVQko{ z*FwGQ)9{wnVh_5x+e6*s!J-gER?861aM~=#`Htfl3c(RT=p`v=d;l#C@{Jb}?81TP{97cJnMzWZmCSP*8Wai&3b0h2SPqpM6cPDb53b8|-^6^4L5cd*uXKHea# z9iR3__~>HyAb}V9oRt*<5?eex1OT?8H};etgMcOX6G(oonq&$g8DS|9{G7ctT0q(x zleesiWHy0C^V^b@O;+~TM*?YbLA3_!v@xLXdJ2Kufl4~Zzp)Z#^DVDwl+r6A`Tk5Z z-k*!;kvS6Tx;rSSAb^b(sJeU=7Hi^iN63_=Sm2#*j=jG_42&|A)O29;^@QN#7*$v+ zdzo9ZX{A9eeE|@+KRI3osrgbjDS0d9&sgig8yGz5=$+rYE(r?zzZeNTE&DRNC8nW- ztmUC)k~%ipvi?HhQeb<++ zyp9b%_uvYf6)}M80TA$D3a(I)ASKfSI_@%tfSE6poWFbF`c5o&2>0=0;h74H7>*s+ zfx~Wf>4%iezeim02q!2?yIN9_8+F)7C4ch#h-?NwvmPxCftu4sE%AsXlOr1loMQHO zZOI4%ITOmCyjXNYNU9L?qH|^u#@B51947}&?bm9?8OCzY=ODdTmNcrw#MD4#4SaId zc3L=USFtmR1_XqLJej-Wp6CKJ{b5=(IH-lvY+u?Xiz3@I|g zw_&86H{GIVA7Avo@eB@Usi(Z{Ch!6}$;UXh+-dDJgxhQF2~T}VZODKIoChZ-n7odAs=GwGSUYx%Yd<&6 zkM48jF*q>I0zhZT=bH#g6#k&8>^pEy9VUuew-06MqvJ0Luv^Fuc2VeL1G`5`J6S zXDU*ttgJd$Za(tOC54hPL|g88-IrR23fFV3NPwlN4E>cw2c@R^>C!)JU}#VTMs)B1 zsLR#M(K^U=t8dngF(})t9#CpMg`+z{x8zKn_-!w6bCtDbz}lG7)UCs$OMAD)r8D?A zIHWX*`*16T@&Xyrfj0Y)qTqxv4iRSPWE!j7&fk%w&J_{i7V!H|qiXeuvI{^is>BrQ zixoS!l7y1a4-~&Zova4gtiO&3Af=ucq27r5tpfvLqd%$S$p8|@ zmoKwboRFP%8s%Bz`oL;Fe%d>lytG>9utaYOUk5OMAfurAWxg}mJQw2F{KYSk72TR~ z?FrDJ=xt`dMX}g!E0_iXD#Zp&0FtL&nb>^Z=IpWU=W7kHy>9OA#N5dgi+97X05;(o zl>k0`u>47F9>kT(GCm#8#}HC8y)x{{cq)l-ue_iqq)88sG1bbJHI{sF4^My+(kqUY zKjOr)`Z{Is(!qSoPcw)C_#_=>llDG*NIbqky>M=^F!50zbgmuPqF8)Cx45Xc7Iap~ zIQ+oX`=;*Ayqo?5tGY`9mQYV*FDX*aCm4lR4Q$||dXYq7ei)o zadR$}NC7u3*5?3)RUW$yy>c~T0dpe2Fkpn%CP;bSG|xKQZ4EXc78e(Pbh%a3N@6GW zvKip-`yunD83ZO=f-C&t_;x~|2+rPjW8Bt~$>;BtIYQ&S7H$le0Aa|2ug0-QIKMjHY*pYRQvUM(>gHk%P!!=Xo zDC%MemlliNLU=eX1gxJ2fGg(mA`Qpc;KU?~S-pEh+e0(e%^9R_i53r3hJQM*ceFe+ z>>(&0L_tA{y1u~9!~ha*PQjtXxnp$>v_v66OMbm-94x*XZf;5?1dWA~`MlH^3ARyC zK`)#?=)DI%lMp`G_V(w5xw=EgyUQkVa7!(1h}(+s7I;=9o!&1FdaYhjRyc?vou7o{ zRvhGfs-5uVzYhGNHb8$UebAEWkD}=7s$q6lCVcaLvxvZfww)i(2CPd2inR;|@@gQt zpXesz4wS3Av8&-8a&pqv176LE7P+44t>>jiN5IO)c2!JUov*15th=Dr^A(VFbN;Zc z-yAXcpkM(~_0Q=9)xwG6mY|gj4i99=>qdCso;uf1gS#s?p$?bT?T<6ZCsvvZrFNuD z>J@Z0c^w@BTQTJdX+x~*L+^n&Led`WzOIp7IN7+t0#t_(W5AB^2WAk7S@(0-3P61J zpR}Ai;M@!30~g?s!&kVu^fl4V=(Q{Q!>lW z|FN}2tgP7(&;_)Wp`BAez-)*SsPA>Jw4A|i+WA3K3T*yjp(Gy^45+?;fAr>+3p;2Z zUrIP1km*;xzj|#_@=g?5NH|0@&zZ;loLXxf(vdhF3tM*McWo)O(-p#%Rm79;x<$;j zmLlgw7@mwxG?(X$YdP@tPxzG&-m@cUX;A??;!gE^C&H5xU?lE^WrA2)vr+1^XoxG8 zu(`umWr%}5e*qO?MVe6)DcpFj?-RxgmndwdV}4xe1RYAdPo(< z&-s;rNsaY(#%ikWbih_y#`3(QrbIl?rVlX#d86RhI+79g40#F1Q{!=Ot5alR%Q`WKu z3WefEN(4fIHu;1Dq8bV)&rU@p{Q7NpU;8wPVHIAG$QT>NV`$@>$tt?MwC3JbRB|-7 zI|hQS@J4>EfzFuo4p6tdVs=ZZC+4aI3nq+0g^vF3bNkwxnnhHqZSg_dk-?da z;NcESDJY}L7MQV-NTvIlf6`!GISNSeQK@tx{t)PC$GG*(74Pnd@sT~{kL-)$cxL5n zt`g>G48Ioln~bF;2rv@F)uVYjdx(WRMH2#L+SL$hQ7nZ<50gNPW(PAMrMmAz`F1=O zRJJ{^w*CJ{MBb-p-|R?SjYPA01(tGDStGgNX}A8gLmSxrR}OV`B;#4Gtt85{=t z)iXxBZmJrIt&jQb+Fj2iRD7GOH5s#%v9C9-8 zz?t!6$3ca>a()`6v`a2~&Y!v|>ct<{+5UaO6CrJ}X<+lnJ6SJE~J2n6!?a zrR>SXGBM?`HsCcA3#CAe=BS*H)jKkN7fi1O&}IW3qdP2x&g~y% zJWfSH$H35EMSSti!qs(H6wK5?ug5d4!sOB0k2DxSn{5HLj(A?dP^B~_yL~SN>TdXD zX6=~WW_U_l#RN8HGRTos*1WTgDzrZTw6)MEm@r+*bm+7&zY#XIS0GJI?#Mj{&z#&l zL%&C*V5s(rEw4l;b8S{x4eFRqF!3zO?bS5Ta{YCdU;Kqf*rgypp${b!d~4JnS$<=< zzG}ByRePS$s&#j&0g66a^c;Rll96MY;3WS~tuG4=W-kA@(`jP4KluF*A}RyZ8`xw; zq?oRh$#CB1>Ta&j<3GVKlf@Qh=@Z`elm$yqUlPPOplC?XnpHmt{YZPILOqU&2ufT( z>5-vnKk_!Ds_dCRWoPrr$vdKjjP#dh z$wYz{=Bs=xIZpv`Ihbkk39svEPSM0d?GNyim0n zC@J85!3!dGcXuBn!1BBHZGlXEAD2{Nb(e69ik%FiKSnY*GUVDjl*?|`+;~qt?M)WE zE$3>EZu4Sxr%L_nZrn_fQHG0jMRuX6J<9pWi!;O{_c@;+Q4W*}ZV(>?1P3&2*_YT) zT`oOa`wJlAbH1SirHDbWw8jA%JJ`ov;*J9nPoF*(21Ww}6_Q5ZA01&)*Hvf23?>e% zMoCKtDWaweP*VID@yR9MR{zI5ph^by&z}W;ge(V`$(k*9P8rNYy%b##4gngrl)VC# zKOT+?c~EfZ^JlE#Wux{U>hjJEnY(k9{+*k2S0~l}&dzqs)$!7Ei#qqe0Zsi3HuD1E zECZv6fY5KI$vPV03*|W;M*AFe6svsHT`mM}ef}}}-Au8Ugz+_^bKwfkWabk6xAq1s*VRodNL*bLZ& zX8s1FL{D&GqXEcG1tfYl&!q@k&DOhHRFIr{vg3$@or&lv=n2dWs@Vg_8%f5;vvva| z=3Ngsfw{cPAXQ78UwGS()%IlN2$e|`3!qk}R*6%9TF@xC02tNls4ox{o)5r7MHQ+!sAVyJK{x#T41P({<5Q{nV|gG*o1< z7qw8B!A(zLiP32{fJczSoep$q8qQt;f*ln-J$i!ldtF`1dx>J!sUi1dx_rlEqJRld z(Xtfi(S!dA^{O;iQVkXvh`E($Aj*F&;gZ3FITCFBe`rvlhJ8CevI62Z6-^Z`^Gk|*Z*fn zgtobcsbPZMVPO#P9uwW4S^qgAZ0-8b6;5#u{;!S*?A3O4G#5#qk=2%1n5ANQ`jdz= zBg)eTdsLMLHnp2$heXTP_1gk^ZQ;!40tr`w3KICO{x~avwC&5*+~N`v%fJ?@Ka&K* z2^=kB2XjFmR4kzB>b;5)|M+n=B=EJq! z0!Ef!7iT{~vc~P?k2ROOzn7xneHEnSqKz>~dv{C1jXLF27+iCle6ogbNK=H-oBWU|3jTiRyR!hH~L=A!lY!$@^nQ zc(~)GpL}B=#ZVzX-M+A;zqOBSEUFyM6;W&7BDu~eSF_+w*kDiv3pe|khqwJ3Zn^Y} zWP5&~U}7w2qsMRGu=C5YjL_GFHJ>fz00r(^KUAp{erAMF5YzxczZ{Oc`3xcnX88ZH zBenoLB40bCO;2|+0JVs3*Dw?nFM7jbFm1%tS-*t=%*=jWg~UpvKa~9-0Kahm$Btms zx!s4cZ7?Y1zqGr|K`RW_-<|9L3`Am(x>BZ~gTH019&C&?9j#3wV`7r!c7{e-fcCfW zUr%1?sbc{q&(G+%19;yyUwkfK#V4=@le6C7kKhK?JoP#gs5zz;S_0xPe`YyofeJgY zRVf{hA#-ZOg=|Yp3xjI%NuPdHNfTo?bpz~(mG|=8(3=bDT0-q!LDnLT9R$Qttn7}V z-&CKy>$Sjk>XM8S=eO1^keUfouIocTUhg`kGq9epE>h(F`MyQ9C#4^F;8LzuY1udL z+X&jFQydKVoZl7<4hZ(IRzP7_c8X(_BEqw)V9QrkTh{MVTItK4MrXOn2%d@-f5==d zl>y8Hm0q!3X)Krg(AoH>k4pxe$nCf#JqV`AIL*f;!(0{Ve$v%H$>IQ#7p$EEgoa3| zYngq?3&t#Y^@_ee*VY;6Un%i4;E+LFivc^rM07$bOa=$*?k#V81M|;Iz>d(2PlVBp z^SV+-O3UgICmvlbwuU`?GeMpFDr31J#{IHQc@!g`mdi$1!CNs`*=~I|Mh~QoN`C*A zZFDFZZA0~P%gxIH6g6>|r;7>Ix>(AV(;ZM%pTG!IBXfRU8%7eUh1`{N0DB{FuEo8o z8b^ApW0uuovB)QcK=kZ3l^JtO34K@+md5GDTXPaUn(6O~nZIS-7R{kjW>8)CEOONr zuLU@NXCn-ARZ7ew6-2RKyUq(|BTg8$WE$A0KpF(SXk`djq*v8IT|-AdDlmD>h*mV#D~YUxNA>Y_&7|_ZNn868Qf<4%F4j z#W8j{GDch_ssDS((v+Z776^zp!f5~T4Em&NaQ~BCeZjl^_b&f?Oa#LJL^u4uKA$C4 zg1syC2L{P(h4u~PDr6tZeYpMa%|YJ2ZIRks^VpWXV(XNJ3R&_{7JwgsBjSt85i6Vy z06EEW9~5%vvHi(Pr+j;sz?YFINxH7hN<7#WD3r@?f(fbV{rv;c5bQz$0goqYeMHA0 zKtIj?*Kzf4l3y1VHx59k+tXi*cd=>BbyEb;HPa-)e-Bo$eg z3rvXndqg1c;Q#)!wS!&P?S>4dr^we$b**2IgY7X0{%)qQMq2!H#b?ae1>QK$ct?Ra zcde0hEpy)G)}&7?5#MLvy{mcvR8(Ps(Z@Gm5F7$}nHj-EGtt$Eu_N^9Vrkey?)M2U zw@x8Y%v7nfwv7BqC>#*mBAE+f%yG2gO}Te$7Z<4`u4$jTFjKhZ7QQQZ(=GoSvjIDz zLyH!yS&TFJipgI^)8&3H=&1ei*gY>|y3njMX}zUthWVZyL6GEmBLJ*T^c8zx5Spwz zl>sF+;6?|KYVmiP`hsrf93Uy_{03%Qowbmatq_dMrE3jx+><|f_blBZJWa>^T*_xY znaBCGqQp4ke;tzssDzYjsDi*DEtoi%?(hP#vw_g~6VlK+;V(3<@;j>3#x&W*>4f|A z=KKcj&?k9lS{Gr1OYl5wBR;l&?1-E`m@g7A%RYGaf*;Q-&`L^oB%{xKvg{rGCdo{V z1t~!gvKNTm=!$+QJnL)kVl`^&$dC=RBA_dPM1RqQWSyHbwIg&4o`jgUMq)G_p-GFo zF(Xb}V=DFN#j*M6q03-spHP~{zN`=mA)v_Qu2C-lieZG1TDL%k{IF` zbCt%oAp6w2)vSyV96;_f-mV)#yaGGC!ZjI&KX6qthoq5+yHKoXd`!*R6+QeV^q(l_ zR8rDEFbu!es zqv|ZpPJup%wA8fE<8{by5gOXMk+#c`I8DV%#jx%l^2ebrvD;tnCy3HgR z>>oQ~91B}4Usk_#y0g18tK`_%k0awjPiqCNfcDzC*I)WSEIa@!m$kU zy}^uNJ$je>_@2+h(dw(*&Ia9GVN|aOeoyXMGg%Rcw?I|qL5FySlPsMuJ-G+H_my6? zMKh|E+>~^X^BFy{xxIQsP96%XGN8m-R%UNB5Yv|_4_CffPt$d$Y2*(CmQbJKen6ppj7zjR74;(l zr2g%Q3%LCr9@yB~!@dLD1nSd2U8b&@BBZk|?&ny-R)gQcs|tLPjs}cY)EUCtu}JUT z+PxXfjA~`c-39Uw%sGxuaegO1D^$DQxn9`WBR`H+xMCl#Fd!_T`t+` zX*f7SfXOm>I&j&%vL1XE)y}fC*np^bRl@-a4HXt? zEb7d!U;NnPt5B8*G8sDV@9JlMolJmy4Fvu7kz+nclcr#Fm9LeysB^FD5|_!1!p#Na ze%^y)Ucy-3#SxTX2IeYndJ;km?z0+W3Mh4TXRKvQjQo+)xQJ2jPTf!keWxbm=j&G3 zY(q?VxDKSka;`f$1^#D2P`ZDeYFmi=%6J`dp_EessQ0Z-osDf!*zDJzAMG|KRhxU= zo{S8LO9)7`ENsAi68|4XM_1dr|DQz1QH@G=(~pf+cVPIDueD?~F8Su@ov+FDE4-%p zkLWvHz>RReF}Zy3RS6Lt{|*5Gtc6l%mrh%7XMhi8;Fn6|v=R*0taNfHtDFu@5vm)< zop*rOmJrl7K=doO8{H!>CUwU}`zIyr9%ys5=g;>>U)tIo<*Yj7l0NMP+=y<)3Sg4~ zZD@QIR*gA!SEv`&Ny?_Y7zDjcuW>W~c656TB)=8O%>&jMCA^?1aU(I5x6RE+{-gJY zd4DspJYOdvK}|$~t_2@ts+Hj56uvtmtepkih-Hzh8RT>*9w*H@?lc5KRP{mht%9XZ zYM5fmzIjt!w+77Y(!CW$An6L(cpx5Gr~79Y$hM-9@$i?=&Mq?xVT;=@L=I=N|1^Tq( z1u8EA9Zi?MlNK+B>CaV}jZBk)Tb>UR(niC-E2qi~U5mqObzm~0 z@7|^UB}e>?dF^Lue2^@gd%0X1wmEx=4btqO4@xLaM4fMspM~ zfPY6!3`Kt4Jq+|Xub$0VIeNZ`j6iB4bA&GWt1jFlElek}r9Rr*XK#?XX+M<-c8^!L ztVXWal7T`w0rKAPsFnf&1{(lr9<bn@Bx2HVciwU z%-!sovw*zdb$BhT4$sPpqCb(p6>R^j!@ZZgf=)z`+CCa9fQ}HClw>Go;^6-Q+H*k9 z`libS*&0dD6yuxVj3ueT9s7%mD@bx3Sc-$YLJi_ps{+NC8&!@{$#NNk+glIta+56LoEGJ zl9~8~(Fk;a7G!8^c#?eOVANJ#w(D5~PCv~t4uZY?bOrr3HP|fxfw}xn1Q{$nGvcy%Iovp2Fg1n`*~iy)YQqy@K3P7 z9I$!@KM8>j;yA85t?VBF2v8p=l1DG@?6GTBgFpC|mG0$>y5+>lF5Y+V=4i)!>y~}~ zxkr%A@)D=~H9s&byzORaDDZ8>0nMHw(aerRC8~JX@>o**o`xz2zrj%WA6uz!D$Om;MT z%rcl`C`s;l!{%|}LBr<=Gnl=I&QfOa^%OkDTxqXwx^2BfZh|-51otQG>$C@3UAis^ zT+NL{zo&5b9UWE3==()&C1E39Z5_Pn2f9-s2gm0?%?1XsI{3W7^nwRpx#?KVsxx5K zDdm=rj(44$1m`mEUkbMUUsO~(&*Jl|-(9;PyzYCN*kgR+4+aKE_ovM6l%3PYZ5aPr z4{#&NzD7>CPGw}`xGgc1@1Ju7WI_bF&IYAK)yc9FNQ9Dv^PUBBafvS+EHw zL`g>VMW^` zBJ$w$eChYQaYc((i6R&BG$;`cLfxO7)HJNO$Jo}kmzospzbT+xCq)rUe*D72Uj#NSIf0Y?C6@S%CJg9)5N zdVA9xHya_92m#s+B!ZrTUuOkqNXe;p}t`vJC$rH>jL1XJ;mYJnkcyf09xa$TnWTgrC9Q@P4CTK)k5T ziMBVU#PAJmE(+MC(v|fIeSj8(f%f8AP*#(xlbCGG_e`&qM^jHJt({izI*u1Zs;ot2 zQNmyqF?yIrAPmg^{~5Ht)k{!W6k-U zrGWxFB)O&}b<0&;3d6!l#i?{+KkCgU0q$UqyZP1;dDy7ssS(}F=AWxig7dnFZ5>;F zx$I9sr{+QYZys0jph`J5KO%ML)S96iMbyIb>Q0OW+rCg)d9WdmdYdCXM)L2i(KdTRsD7S zFmPT@T(i(i_z!;S9}w4+DU2PMDYySVHw55WAm85LDbM(#En9smE(HbZk1k*0u)g*d zv@p{4Sq);pcS0aHG_RGqdWXe$A#*7RKf{ zs{)^A4}peYuE_Iz`;El6wc=P3i%<0vVMYdvCSqds6Q(^1L=3U@9)5Jm-Sag{ol(pz z5BCU{R}=a769m@N00_LR7jN=s4Fc_J8J+V2P3~A5B5$Hs$RT1rb{qX^CEcTowgf>~tmNtb5vt7hmzh31D6%%W$u>J+-Eyt{TrPSJI=k zg~PC)K zB9YCdEjfTf2DG9!wt!>{P%jCf6hTQ#O>Lp|QolB$hc&`IfM)YhwIghC_VV~s?kEwb zIUOUxLh#;QlvOvpt`r&VoJt3%uakE}{@ix!m^nGQj~5UFIc>%eiUT;n zo%sbR#a=m%5F{y0FU8Ajq$hm1S5+wAR37om`+TY&!?vry5Pr6p-L*xfh=@-yKDXOD z#q4~Qpn?!-tS*3ZR{Z(pFK$XS!Mk4}U%m+YeucKCfn9F>2{)9Shkn5x1B2D&#Dr0^ zglNOgj?HpcC75W5FUHQv2}$7Ypm6f`oMnjr`2!gijeykD-1);osqDO0lq(0iAH^kF zYRVZ7=MGnh_)VXHdYaAA>W7=xe5fOt0$KT|s)eAuQ(nU{Q&oNW z6_YqPlkiPo#IT&(^;{rLWn6SLYFLL4@#~D{J?INgD7Vo-;@GyGmXhwDp|tnmKY#jG z!ujw~8+ZbQ@(I$!LPTMHK>{OzxO3YLw`b}qc?53&-%>1?2n{UhlKKgYEdaKR$zDSa>z&jUQ#k6vBo?O~Y! zv%*lgyy#u1uGqk{-hVBV=dy?YF_!%isP_$;uounj(J?c(7wbD{rsI%xH-Q;2D~MkK zAr-%RjWnulk@*}w*84E`W$s4^bEVhA!)JQyc`;7*SC~)5;Wf?9It28EV>X|M+&lP{Bbr)Vt?P1kNsR4RqV z6j$$i=7i0LIO|Ozav};$*EC^KlWVZV@(+m9vazxG#clH(%#Y1ENj+{)mCYxshOW~# z`y8@W8evk5YA1}K=Mv1Ui}r5M4{z)S#?&WhlXjax0i?)Ag#HaV4)eyEZeJ|d&2&;` zk~4y*vn!sQT*N)|qRG5?@p@lou??<9Pm$aARrbaErSJX}U*)nz6&3M^M{so3VjUn} zefxDm9=BJzZQmvq?RnYk#^Y%!tIY;HK=y|oI17KVe}0Vl^|3jK6qAgqo7_IWt9{ql zk;Ik%UQh2i$k#y096|PJ_OjcsyKMfgK0NI}S`3|QM_Cv#VOOa$cp24LGYn@rmd|8! zxY|WoAWc~0gCD2Yx%E2RzV%r{#SXDjylf9WRLh-L2~)JnvnuOtF^-*>NxL60j%(Bo zUZ17!wSl?R{d9@2e?wUNN16yBzTaDsRUVmh@YL9s^{_4bDKa51lt_HTD*M9+H46@e zHy@~Wqr4;uS(5AWo-kLIPT4g*@RE4p&qa26O%kqdGQ%6t$L~5{8@l<6bb+5|W0&|( zNI-`O4*!>dTqq}q^!_ak5)WOF+P|T%#`BM?UqAO16&*99-?3M_zmVAtaaNsnZ z`Pg#j(HBqr=n7ZE1rK$cn;e$CVJ=$#R)oZ#3=ET~!Im|^E|Gj&A-A^uN6 zlhA7=^iI>>9q%MUCb;%mku8BkX|Tnq1M}Rz-#e6HNL$IN^HBky*@lC z4N~>qqgpEXKF?#Kt=fP&G&Hn1wjr&ADN8%TFX!*;i)7WLw$pN{0o;^VWxbsKuY|BH zyyBCnmJ(~(o-;S7zRgpAP%;qTei*B(rj(mLg^(wl?xjTfFn4Tt83OpQPb2a1E#wNn z^O70$M^M;X&Qz55tqX^QR+E7NI!Fe(Yf|D0lkACJGHKV{CmO5ZCbvjNdH))FWw}j5 zfh4zX-~X#aYm>JR8>h{iJOWa1H5&goZU=PiYlD^sErR95%B=%CEpm08+L~@nBP1%M zw|okgO!*dp`GgSdveZ{Ea8vBIVZ8T4w;qBJIUL00yHlJ>mzC4xZ+qfW{>Rej) zcrtIQFZR{f>49a!#N256?h!4Sw!kdKZi$o0qzcsQk+Wpi-~HEWW>ve`-0$!e=#gKQ zO1G?Ab512p>g2Vj2N42Ke%O)Kr_8zi#j!Og)MRI;+*Dl;p-lB)dZ+`07AaBD(&~ee zB3SAvj{ZAjdIknu5|Yp&YvfX`!FVSs>CO(Jqih#&CFX+#eKtw z3%)$;DcTwZUrOi^ME2-}F(vSMGGfj`iO8#~Mv4y~0_i$?+5&q&_9pTr(PsGUPTdkO zuPi5jqWc6AYS2^S#IC#%=@?jY&(%Bx3YS8oDy#&VCqBC2K*$3T7>D|Bdn@prd(Cmd zR!JKhzi3Q;=zjetIkSM)))od;&2Ng0YOA%brEOv=3 zMFi{W1Zx#O<>W~P3CRrgIA+io8*rWAXxQpQbY@_eiU@wEfY;LCl|97&11YpM#2UzLq`N$#I@q049sYm zXX=vp>B0v-rY3l6maXNg-wMZO*@{HLKl`2I&>;LL`C8F4dtX+gbQ~wA*{v;l*osEk zL%q`pj=9&OR60ly9?4`SOFc=EQb2IQpG83nm2(cxtMPxI$X7v=@`FQ!{CGOTvi1J` zMjgNzs4zp8_s|p{rAQ&HbQ0~BhW3v*ep#C0Lvj>4mv4jZ<20s^mpH{)4Ljx6Sgomn zqLZo~_$=VSiAw1YR+ceJ(WrUqz$Mxo z4t7KmgLmDW99D+1#~{xsEt-8rcfRwr!!@p;5OQ(hNwBtcY`UxKAi3b#OyG7XAOKp` zS?Lm?E0)1L)3FEWQy>5~_tLjTFm7j8e7G>D6Ks;wuFku^Jm=U5^-NV`Va*a0lHu}| zU_UAZ@`zxmTX?~1vlGo`SGo#-CGIY%VTzc3nRTmI%T+x_2A0OD5O=u9 zI)`upFDv0?-%z%jS8J;j-*T4iDjT;!tY4Ab6z9pyKtIun&ux0{V z77q%x`3q=sd^!g)U+^c(dT>fr5;2%I{d8f9hszZIFy*aYts6QxSXeR^glI+ Host: ) services.append(service) + # Parse IP addresses - handle both new HostIP format and legacy formats + ip_addresses = [] + if 'ip_addresses' in host_data: + for ip_data in host_data['ip_addresses']: + if isinstance(ip_data, dict): + # New HostIP format: {ip_address: "192.168.1.10", network_segment: "LAN", is_primary: true} + host_ip = HostIP( + ip_address=ip_data['ip_address'], + network_segment=ip_data.get( + 'network_segment', 'LAN'), # Default segment + is_primary=ip_data.get('is_primary', False) + ) + ip_addresses.append(host_ip) + else: + # Legacy format: simple string list + host_ip = HostIP( + ip_address=ip_data, + network_segment='LAN', # Default segment for legacy format + is_primary=len(ip_addresses) == 0 # First IP is primary + ) + ip_addresses.append(host_ip) + elif 'ip_address' in host_data: + # Very old format: single IP string + host_ip = HostIP( + ip_address=host_data['ip_address'], + network_segment='LAN', + is_primary=True + ) + ip_addresses.append(host_ip) + # Create host host = Host( name=host_data['name'], - ip_address=host_data['ip_address'], + ip_addresses=ip_addresses, host_type=parse_host_type(host_data['host_type']), + icon=host_data.get('icon'), # Custom icon name description=host_data.get('description', ''), services=services ) @@ -93,6 +124,34 @@ def parse_host(host_data: Dict[str, Any]) -> Host: def parse_location(location_data: Dict[str, Any]) -> Location: """Parse a location from YAML data.""" + # Parse network segments + network_segments = [] + if 'network_segments' in location_data: + for segment_data in location_data['network_segments']: + segment = NetworkSegment( + name=segment_data['name'], + cidr=segment_data['cidr'], + vlan_id=segment_data.get('vlan_id'), + zone=segment_data.get('zone', 'general'), + gateway=segment_data.get('gateway'), + description=segment_data.get('description', '') + ) + network_segments.append(segment) + + # Parse port forwardings + port_forwardings = [] + if 'port_forwardings' in location_data: + for pf_data in location_data['port_forwardings']: + port_forward = PortForwarding( + external_port=pf_data['external_port'], + internal_ip=pf_data['internal_ip'], + internal_port=pf_data['internal_port'], + protocol=pf_data.get('protocol', 'tcp'), + description=pf_data.get('description', ''), + enabled=pf_data.get('enabled', True) + ) + port_forwardings.append(port_forward) + # Parse hosts hosts = [] if 'hosts' in location_data: @@ -108,6 +167,10 @@ def parse_location(location_data: Dict[str, Any]) -> Location: active=False, # Runtime state - always starts inactive vpn_config=location_data.get('vpn_config', ''), hosts=hosts, + network_segments=network_segments, + networks=location_data.get('networks', []), # Legacy support + external_addresses=location_data.get('external_addresses', []), + port_forwardings=port_forwardings, vpn_credentials=location_data.get('vpn_credentials'), nmcli_connection_name=location_data.get('nmcli_connection_name'), auto_import=location_data.get('auto_import', True) @@ -204,20 +267,64 @@ def save_customer(customer: Customer, filename: str = None) -> None: # Convert locations for location in customer.locations: + # Convert network segments + network_segments = [] + for segment in location.network_segments: + segment_data = { + 'name': segment.name, + 'cidr': segment.cidr, + 'zone': segment.zone, + 'description': segment.description + } + if segment.vlan_id is not None: + segment_data['vlan_id'] = segment.vlan_id + if segment.gateway is not None: + segment_data['gateway'] = segment.gateway + network_segments.append(segment_data) + + # Convert port forwardings + port_forwardings = [] + for pf in location.port_forwardings: + pf_data = { + 'external_port': pf.external_port, + 'internal_ip': pf.internal_ip, + 'internal_port': pf.internal_port, + 'protocol': pf.protocol, + 'enabled': pf.enabled + } + if pf.description: + pf_data['description'] = pf.description + port_forwardings.append(pf_data) + location_data = { 'name': location.name, 'vpn_type': location.vpn_type.value, 'vpn_config': location.vpn_config, - 'active': location.active, - 'connected': location.connected, + 'network_segments': network_segments, + 'external_addresses': location.external_addresses, + 'port_forwardings': port_forwardings, 'hosts': [] } + # Add legacy networks if they exist + if location.networks: + location_data['networks'] = location.networks + # Convert hosts def convert_host(host): + # Convert HostIP objects back to dictionaries + ip_addresses = [] + for host_ip in host.ip_addresses: + ip_dict = { + 'ip_address': host_ip.ip_address, + 'network_segment': host_ip.network_segment, + 'is_primary': host_ip.is_primary + } + ip_addresses.append(ip_dict) + host_data = { 'name': host.name, - 'ip_address': host.ip_address, + 'ip_addresses': ip_addresses, 'host_type': host.host_type.value, 'description': host.description, 'services': [ @@ -230,6 +337,10 @@ def save_customer(customer: Customer, filename: str = None) -> None: ] } + # Add icon if specified + if host.icon: + host_data['icon'] = host.icon + if host.sub_hosts: host_data['sub_hosts'] = [convert_host( subhost) for subhost in host.sub_hosts] @@ -267,12 +378,22 @@ def get_demo_customers() -> List[Customer]: vpn_type=VPNType.OPENVPN, connected=False, active=True, - vpn_config="/etc/openvpn/demo.ovpn" + vpn_config="demo.ovpn" # File in ~/.vpntray/vpn/ + ) + + # Create a demo network segment + demo_segment = NetworkSegment( + name="LAN", + cidr="10.0.0.0/24", + gateway="10.0.0.1", + zone="production", + description="Demo network" ) demo_host = Host( name="DEMO-01", - ip_address="10.0.0.1", + ip_addresses=[HostIP(ip_address="10.0.0.1", + network_segment="LAN", is_primary=True)], host_type=HostType.LINUX, description="Demo server", services=[ @@ -282,6 +403,7 @@ def get_demo_customers() -> List[Customer]: ) demo_location.hosts = [demo_host] + demo_location.network_segments = [demo_segment] demo_customer.locations = [demo_location] return [demo_customer] @@ -317,13 +439,24 @@ services: locations: - name: Main Office vpn_type: WireGuard - vpn_config: /etc/wireguard/simple.conf - active: false - connected: false + vpn_config: simple.conf # File in ~/.vpntray/vpn/ + + network_segments: + - name: LAN + cidr: 192.168.1.0/24 + gateway: 192.168.1.1 + zone: production + description: Main office network + + external_addresses: + - simple.vpn.example.com hosts: - name: SERVER-01 - ip_address: 192.168.1.10 + ip_addresses: + - ip_address: 192.168.1.10 + network_segment: LAN + is_primary: true host_type: Linux description: Main server services: diff --git a/example_customer.yaml b/example_customer.yaml index 60b2d30..7447371 100644 --- a/example_customer.yaml +++ b/example_customer.yaml @@ -22,7 +22,72 @@ services: locations: - name: Main Office vpn_type: OpenVPN - vpn_config: /etc/openvpn/techcorp-main.ovpn + vpn_config: techcorp-main.ovpn # File in ~/.vpntray/vpn/ + + # External connection endpoints (can have multiple for redundancy) + external_addresses: + - vpn.techcorp.com # Primary VPN endpoint + - vpn2.techcorp.com # Backup endpoint + - 203.0.113.10 # Direct IP fallback + + # Port forwarding rules for external access + port_forwardings: + - external_port: 8006 + internal_ip: 192.168.1.10 + internal_port: 8006 + protocol: tcp + description: Proxmox web interface + enabled: true + + - external_port: 3389 + internal_ip: 192.168.1.20 + internal_port: 3389 + protocol: tcp + description: Domain Controller RDP + enabled: true + + - external_port: 9000 + internal_ip: 192.168.1.21 + internal_port: 9000 + protocol: tcp + description: File server web panel + enabled: true + + - external_port: 5050 + internal_ip: 192.168.1.22 + internal_port: 5050 + protocol: tcp + description: pgAdmin database interface + enabled: true + + - external_port: 443 + internal_ip: 192.168.1.1 + internal_port: 443 + protocol: tcp + description: Firewall web interface + enabled: true + + # Network segments with rich metadata + network_segments: + - name: LAN + cidr: 192.168.1.0/24 + gateway: 192.168.1.1 + zone: production + description: Main office LAN + + - name: Management + cidr: 10.0.1.0/24 + vlan_id: 100 + gateway: 10.0.1.1 + zone: management + description: Out-of-band management network + + - name: Services + cidr: 172.16.1.0/24 + vlan_id: 200 + gateway: 172.16.1.1 + zone: production + description: Internal services network # VPN credentials - three options: # Option 1: Dictionary with username/password @@ -39,8 +104,12 @@ locations: # Hosts at this location hosts: - name: PVE-01 - ip_address: 192.168.1.10 + ip_addresses: + - ip_address: 192.168.1.10 + network_segment: LAN + is_primary: true host_type: Proxmox + icon: proxmox # Custom icon: assets/icons/proxmox.svg description: Main virtualization server services: - name: Web Interface @@ -53,7 +122,10 @@ locations: # VMs running on this host sub_hosts: - name: DC-01 - ip_address: 192.168.1.20 + ip_addresses: + - ip_address: 192.168.1.20 + network_segment: LAN + is_primary: true host_type: Windows Server description: Domain Controller services: @@ -65,8 +137,12 @@ locations: port: 8080 - name: FILE-01 - ip_address: 192.168.1.21 + ip_addresses: + - ip_address: 192.168.1.21 + network_segment: LAN + is_primary: true host_type: Linux + icon: ubuntu # Custom icon: assets/icons/ubuntu.svg description: File Server (Samba) services: - name: SSH @@ -80,9 +156,15 @@ locations: port: 9000 - name: DB-01 - ip_address: 192.168.1.22 + ip_addresses: + - ip_address: 192.168.1.22 + network_segment: LAN + is_primary: true + - ip_address: 172.16.1.22 + network_segment: Services + is_primary: false host_type: Linux - description: PostgreSQL Database + description: PostgreSQL Database (dual-homed) services: - name: SSH service_type: SSH @@ -95,9 +177,19 @@ locations: port: 5050 - name: FW-01 - ip_address: 192.168.1.1 + ip_addresses: + - ip_address: 192.168.1.1 + network_segment: LAN + is_primary: true + - ip_address: 10.0.1.1 + network_segment: Management + is_primary: false + - ip_address: 172.16.1.1 + network_segment: Services + is_primary: false host_type: Router - description: pfSense Firewall/Router + icon: pfsense # Custom icon: assets/icons/pfsense.svg + description: pfSense Firewall/Router (multi-interface) services: - name: Web Interface service_type: Web GUI @@ -107,9 +199,15 @@ locations: port: 22 - name: SW-01 - ip_address: 192.168.1.2 + ip_addresses: + - ip_address: 192.168.1.2 + network_segment: LAN + is_primary: true + - ip_address: 10.0.1.2 + network_segment: Management + is_primary: false host_type: Switch - description: Managed Switch + description: Managed Switch (dual-homed) services: - name: Web Interface service_type: Web GUI @@ -120,16 +218,57 @@ locations: - name: Branch Office vpn_type: WireGuard - vpn_config: /etc/wireguard/techcorp-branch.conf + vpn_config: techcorp-branch.conf # File in ~/.vpntray/vpn/ + + # External connection endpoints + external_addresses: + - 198.51.100.50 # Branch office static IP + - branch.techcorp.com # Dynamic DNS endpoint + + # Port forwarding rules + port_forwardings: + - external_port: 8080 + internal_ip: 10.10.1.10 + internal_port: 8080 + protocol: tcp + description: Branch web services + enabled: true + + - external_port: 22 + internal_ip: 10.10.1.10 + internal_port: 22 + protocol: tcp + description: SSH access to branch server + enabled: false # Disabled for security + + # Network segments + network_segments: + - name: Branch_LAN + cidr: 10.10.1.0/24 + gateway: 10.10.1.1 + zone: production + description: Branch office network + + - name: Local_Services + cidr: 192.168.100.0/24 + gateway: 192.168.100.1 + zone: general + description: Local branch services network # No credentials needed for WireGuard (uses keys in config file) vpn_credentials: null hosts: - name: BRANCH-01 - ip_address: 10.10.1.10 + ip_addresses: + - ip_address: 10.10.1.10 + network_segment: Branch_LAN + is_primary: true + - ip_address: 192.168.100.1 + network_segment: Local_Services + is_primary: false host_type: Linux - description: Branch office server + description: Branch office server (dual-homed) services: - name: SSH service_type: SSH diff --git a/init_config.py b/init_config.py index 12816d9..3159f4f 100644 --- a/init_config.py +++ b/init_config.py @@ -12,12 +12,12 @@ from data_loader import initialize_example_customers, get_config_dir def main(): """Initialize VPNTray configuration with example customers.""" config_dir = get_config_dir() - + print("VPNTray Configuration Initializer") print("=" * 35) print(f"Configuration directory: {config_dir}") print() - + try: initialize_example_customers() print() @@ -32,11 +32,11 @@ def main(): print("- Each customer gets their own .yaml/.yml file") print("- File names don't matter (use descriptive names)") print("- See example_customer.yaml for the complete schema") - + except Exception as e: print(f"❌ Error initializing configuration: {e}") sys.exit(1) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/main.py b/main.py index b29df67..bc300f6 100644 --- a/main.py +++ b/main.py @@ -1,22 +1,27 @@ #!/usr/bin/env python3 -from views import ActiveView, InactiveView +from views import ActiveView, InactiveView, LogView from data_loader import load_customers from models import Customer -from PIL import Image, ImageDraw -import pystray -import threading +# from services import VPNManager, VPNStatus, VPNConnectionError # Temporarily disabled due to syntax errors +from services import VPNManager, VPNStatus, VPNConnectionError import sys -from gi.repository import Gtk, Gdk, GLib +import logging +from gi.repository import Gtk, Gdk, GLib, Gio import gi gi.require_version('Gtk', '3.0') class VPNManagerWindow: + vpn_manager: VPNManager + def __init__(self): self.customers = load_customers() self.filtered_customers = self.customers.copy() self.current_location = None # Track user's current location + # VPN manager will be initialized after UI setup + self.vpn_manager = None + # Create main window self.window = Gtk.Window() self.window.set_title("VPN Manager") @@ -24,30 +29,18 @@ class VPNManagerWindow: self.window.connect("delete-event", self.quit_app_from_close) self.window.connect("window-state-event", self.on_window_state_event) -# Set up minimal CSS for GNOME-style cards + # Set up minimal CSS for GNOME-style cards self.setup_css() # Create UI self.setup_ui() - self.setup_system_tray() - - # Start hidden - self.window.hide() + self.vpn_manager = VPNManager() def setup_css(self): """Minimal CSS for GNOME-style cards""" css_provider = Gtk.CssProvider() - css = """ - .card { - background: @theme_base_color; - border-radius: 8px; - border: 1px solid @borders; - box-shadow: 0 1px 3px rgba(0,0,0,0.1); - padding: 16px; - margin: 6px; - } - """ - css_provider.load_from_data(css.encode()) + css_provider.load_from_file(Gio.File.new_for_path('style.css')) + # css_provider.load_from_data(css.encode()) # Apply CSS to default screen screen = Gdk.Screen.get_default() @@ -72,12 +65,9 @@ class VPNManagerWindow: main_vbox.set_margin_bottom(12) self.window.add(main_vbox) - # Current location display - self.current_location_label = Gtk.Label() - self.current_location_label.set_markup("Current location: Not set") - self.current_location_label.set_halign(Gtk.Align.CENTER) - self.current_location_label.set_margin_bottom(8) - main_vbox.pack_start(self.current_location_label, False, False, 0) + # Current location display - enhanced info box + self.location_info_box = self._create_location_info_box() + main_vbox.pack_start(self.location_info_box, False, False, 0) # Search bar with SearchEntry self.search_entry = Gtk.SearchEntry() @@ -91,62 +81,65 @@ class VPNManagerWindow: self.view_stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) self.view_stack.set_transition_duration(200) main_vbox.pack_start(self.view_stack, True, True, 0) - + # Get callbacks for views callbacks = self.get_callbacks() - + # Create active view (shown by default) self.active_view = ActiveView(callbacks) self.view_stack.add_named(self.active_view.widget, "active") - + # Create inactive view (shown when searching) self.inactive_view = InactiveView(callbacks) self.view_stack.add_named(self.inactive_view.widget, "inactive") + # Create log section at bottom (collapsible) + self._create_log_section(main_vbox) + + # Initialize VPN manager (temporarily disabled due to syntax errors) + # TODO: Fix VPN manager syntax and re-enable + self.vpn_manager = None + self.log_view.log_info( + "VPN manager temporarily disabled for debugging") + self.log_view.log_info("Using mock mode for VPN operations") + # Render initial data self.render_customers() - def setup_system_tray(self): - # Create a simple icon for the system tray - def create_icon(): - # Create a simple network icon - width = height = 64 - image = Image.new('RGBA', (width, height), (0, 0, 0, 0)) - draw = ImageDraw.Draw(image) + # Update VPN status from actual connections + self.update_vpn_status() - # Draw a simple network/VPN icon - # Outer circle - draw.ellipse([8, 8, 56, 56], outline=(50, 150, 50), width=4) - # Inner dot - draw.ellipse([26, 26, 38, 38], fill=(50, 150, 50)) - # Connection lines - draw.line([32, 16, 32, 24], fill=(50, 150, 50), width=3) - draw.line([32, 40, 32, 48], fill=(50, 150, 50), width=3) - draw.line([16, 32, 24, 32], fill=(50, 150, 50), width=3) - draw.line([40, 32, 48, 32], fill=(50, 150, 50), width=3) + def _setup_logging(self): + """Set up logging to route VPN manager logs to LogView.""" + # Create a custom handler that forwards to our LogView + class LogViewHandler(logging.Handler): + def __init__(self, log_view): + super().__init__() + self.log_view = log_view - return image + def emit(self, record): + try: + msg = self.format(record) + if record.levelno >= logging.ERROR: + self.log_view.log_error(msg) + elif record.levelno >= logging.WARNING: + self.log_view.log_warning(msg) + elif record.levelno >= logging.INFO: + self.log_view.log_info(msg) + else: # DEBUG + self.log_view.log_debug(msg) + except Exception: + self.handleError(record) - # Simple approach: Create tray icon with direct action and minimal menu - self.tray_icon = pystray.Icon( - "VPN Manager", - create_icon(), - "VPN Manager - Double-click to open" - ) + # Set up handler for VPN manager logs + handler = LogViewHandler(self.log_view) + handler.setFormatter(logging.Formatter('%(message)s')) - # Set direct click action - self.tray_icon.default_action = self.show_window_from_tray - - # Also provide a right-click menu - menu = pystray.Menu( - pystray.MenuItem("Open VPN Manager", - self.show_window_from_tray, default=True), - pystray.MenuItem("Quit", self.quit_app) - ) - self.tray_icon.menu = menu - - # Start tray icon in separate thread - threading.Thread(target=self.tray_icon.run, daemon=True).start() + # Add handler to VPN manager logger + vpn_logger = logging.getLogger('services.vpn_manager') + vpn_logger.addHandler(handler) + vpn_logger.setLevel(logging.DEBUG) + vpn_logger.propagate = False # Don't send to root logger def get_callbacks(self): """Return callback functions for widget interactions""" @@ -204,10 +197,12 @@ class VPNManagerWindow: target_location = customer.get_location_by_name(location.name) if target_location: target_location.active = True + self.log_view.log_info( + f"Activated location: {customer.name} - {target_location.name}") print( f"Mock: Setting {customer.name} - {target_location.name} as active") break - + # Clear search and return to active view self.search_entry.set_text("") self.render_customers() @@ -219,35 +214,322 @@ class VPNManagerWindow: if target_location: target_location.active = False target_location.connected = False # Disconnect when deactivating + self.log_view.log_info( + f"Deactivated location: {customer.name} - {target_location.name}") print( f"Mock: Deactivating {customer.name} - {target_location.name}") break self.render_customers() - + def set_current_location(self, location, customer_name): """Set the user's current location.""" for customer in self.customers: if customer.name == customer_name: target_location = customer.get_location_by_name(location.name) if target_location: - self.current_location = (customer.name, target_location.name) - print(f"Current location set to: {customer.name} - {target_location.name}") + self.current_location = ( + customer.name, target_location.name) + self.log_view.log_info( + f"Current location set to: {customer.name} - {target_location.name}") + print( + f"Current location set to: {customer.name} - {target_location.name}") self.update_current_location_display() break - + + def _create_location_info_box(self): + """Create the enhanced current location info box.""" + frame = Gtk.Frame() + frame.get_style_context().add_class("location-info") + frame.set_shadow_type(Gtk.ShadowType.NONE) + + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4) + frame.add(vbox) + + # Title row with infrastructure toggle + title_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + vbox.pack_start(title_box, False, False, 0) + + title_label = Gtk.Label() + title_label.set_markup("📍 Current Location") + title_label.set_halign(Gtk.Align.START) + title_box.pack_start(title_label, False, False, 0) + + # Infrastructure toggle button (only shown when location is set) + self.infrastructure_toggle = Gtk.Button() + self.infrastructure_toggle.set_relief(Gtk.ReliefStyle.NONE) + self.infrastructure_toggle.set_can_focus(False) + self.infrastructure_toggle.set_label("▶") + self.infrastructure_toggle.set_tooltip_text("Show/hide infrastructure") + self.infrastructure_toggle.connect( + "clicked", self._on_infrastructure_toggle) + self.infrastructure_toggle.set_visible(False) + title_box.pack_end(self.infrastructure_toggle, False, False, 0) + + # Location details label + self.location_details_label = Gtk.Label() + self.location_details_label.set_markup("Not set") + self.location_details_label.set_halign(Gtk.Align.START) + vbox.pack_start(self.location_details_label, False, False, 0) + + # Additional info row (hosts, services, etc.) + self.location_extra_info = Gtk.Label() + self.location_extra_info.set_halign(Gtk.Align.START) + self.location_extra_info.set_visible(False) + vbox.pack_start(self.location_extra_info, False, False, 0) + + # Infrastructure section (collapsible) + self.infrastructure_box = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=6) + self.infrastructure_box.set_margin_top(8) + self.infrastructure_box.set_visible(False) + vbox.pack_start(self.infrastructure_box, False, False, 0) + + # Track infrastructure expanded state + self.infrastructure_expanded = False + + return frame + + def _create_log_section(self, main_vbox): + """Create the collapsible log section at the bottom.""" + # Log section container + log_container = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=0) + log_container.get_style_context().add_class("log-section") + main_vbox.pack_end(log_container, False, False, 0) + + # Log header with toggle button + log_header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + log_header.set_margin_start(12) + log_header.set_margin_end(12) + log_header.set_margin_top(8) + log_header.set_margin_bottom(8) + log_container.pack_start(log_header, False, False, 0) + + # Toggle button for log visibility + self.log_toggle = Gtk.Button() + self.log_toggle.set_relief(Gtk.ReliefStyle.NONE) + self.log_toggle.set_can_focus(False) + self.log_toggle.set_label("▲") + self.log_toggle.set_tooltip_text("Show/hide command log") + self.log_toggle.connect("clicked", self._on_log_toggle) + log_header.pack_start(self.log_toggle, False, False, 0) + + # Log section label + log_section_label = Gtk.Label() + log_section_label.set_markup("Command Log") + log_section_label.set_halign(Gtk.Align.START) + log_header.pack_start(log_section_label, False, False, 0) + + # Create the log view + self.log_view = LogView() + log_container.pack_start(self.log_view.widget, False, False, 0) + + # Start with log collapsed + self.log_expanded = False + self.log_view.set_visible(False) + + # Log some initial messages + self.log_view.log_info("VPN Manager started") + self.log_view.log_info(f"Loaded {len(self.customers)} customers") + + def _on_log_toggle(self, button): + """Toggle log section visibility.""" + self.log_expanded = not self.log_expanded + + if self.log_expanded: + self.log_toggle.set_label("▼") + self.log_view.set_visible(True) + else: + self.log_toggle.set_label("▲") + self.log_view.set_visible(False) + def update_current_location_display(self): - """Update the current location display label.""" + """Update the current location display with detailed information.""" if self.current_location: customer_name, location_name = self.current_location - self.current_location_label.set_markup( - f"📍 Current location: {customer_name} - {location_name}" - ) + + # Find the actual location object + location = None + for customer in self.customers: + if customer.name == customer_name: + location = customer.get_location_by_name(location_name) + if location: + break + + if location: + # Main location info + self.location_details_label.set_markup( + f"{customer_name} - {location_name}" + ) + + # Extra info about the location + host_count = len(location.hosts) + total_hosts = len(location.get_all_hosts_flat()) + vpn_type = location.vpn_type.value + + extra_text = f"{vpn_type} VPN" + if location.external_addresses: + if len(location.external_addresses) == 1: + extra_text += f" • 🌐 {location.external_addresses[0]}" + else: + extra_text += f" • 🌐 {len(location.external_addresses)} endpoints" + if location.networks: + extra_text += f" • 📡 {len(location.networks)} network{'s' if len(location.networks) > 1 else ''}" + extra_text += f" • {host_count} hosts" + if total_hosts > host_count: + extra_text += f" ({total_hosts} total with VMs)" + extra_text += "" + + self.location_extra_info.set_markup(extra_text) + self.location_extra_info.set_visible(True) + + # Show infrastructure toggle and rebuild infrastructure + self.infrastructure_toggle.set_visible(True) + self._rebuild_infrastructure_display(location) + else: + self.location_details_label.set_markup( + f"{customer_name} - {location_name}" + ) + self.location_extra_info.set_visible(False) + self.infrastructure_toggle.set_visible(False) else: - self.current_location_label.set_markup("Current location: Not set") + self.location_details_label.set_markup("Not set") + self.location_extra_info.set_visible(False) + self.infrastructure_toggle.set_visible(False) + self.infrastructure_box.set_visible(False) + + def _rebuild_infrastructure_display(self, location): + """Rebuild the infrastructure display for the current location.""" + # Clear existing infrastructure widgets + for child in self.infrastructure_box.get_children(): + child.destroy() + + # Add network information if available + if location.networks or location.external_addresses: + network_label = Gtk.Label() + network_label.set_markup("Network Configuration") + network_label.set_halign(Gtk.Align.START) + network_label.set_margin_bottom(4) + self.infrastructure_box.pack_start(network_label, False, False, 0) + + # External addresses + if location.external_addresses: + for i, address in enumerate(location.external_addresses): + ext_box = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + ext_box.set_margin_start(12) + self.infrastructure_box.pack_start( + ext_box, False, False, 0) + + label_text = "🌐 External:" if i == 0 else "🌐 Backup:" + ext_label = Gtk.Label() + ext_label.set_markup( + f"{label_text} {address}") + ext_label.set_halign(Gtk.Align.START) + ext_box.pack_start(ext_label, False, False, 0) + + # Internal networks + if location.networks: + for network in location.networks: + net_box = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + net_box.set_margin_start(12) + self.infrastructure_box.pack_start( + net_box, False, False, 0) + + net_label = Gtk.Label() + net_label.set_markup( + f"📡 Network: {network}") + net_label.set_halign(Gtk.Align.START) + net_box.pack_start(net_label, False, False, 0) + + # Add spacing before infrastructure + if location.hosts: + spacer = Gtk.Box() + spacer.set_size_request(-1, 8) + self.infrastructure_box.pack_start(spacer, False, False, 0) + + if not location.hosts: + return + + # Add infrastructure label + infra_label = Gtk.Label() + infra_label.set_markup("Infrastructure") + infra_label.set_halign(Gtk.Align.START) + infra_label.set_margin_bottom(4) + self.infrastructure_box.pack_start(infra_label, False, False, 0) + + # Add hosts + for host in location.hosts: + host_box = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + host_box.set_margin_start(12) + self.infrastructure_box.pack_start(host_box, False, False, 0) + + # Host type icon + host_type_icons = { + 'Linux': '🐧', + 'Windows': '🪟', + 'Windows Server': '🏢', + 'Proxmox': '📦', + 'ESXi': '⚙️', + 'Router': '📡', + 'Switch': '🔀' + } + icon = host_type_icons.get(host.host_type.value, '💻') + + # Host info + host_label = Gtk.Label() + service_count = len(host.services) + vm_count = len(host.sub_hosts) + + host_text = f"{icon} {host.name} ({host.ip_address})" + if service_count > 0: + host_text += f" • {service_count} services" + if vm_count > 0: + host_text += f" • {vm_count} VMs" + + host_label.set_markup(f"{host_text}") + host_label.set_halign(Gtk.Align.START) + host_box.pack_start(host_label, False, False, 0) + + # Add sub-hosts (VMs) if any + if host.sub_hosts: + for vm in host.sub_hosts: + vm_box = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + vm_box.set_margin_start(24) + self.infrastructure_box.pack_start(vm_box, False, False, 0) + + vm_icon = host_type_icons.get(vm.host_type.value, '💻') + vm_service_count = len(vm.services) + + vm_text = f"{vm_icon} {vm.name} ({vm.ip_address})" + if vm_service_count > 0: + vm_text += f" • {vm_service_count} services" + + vm_label = Gtk.Label() + vm_label.set_markup(f"{vm_text}") + vm_label.set_halign(Gtk.Align.START) + vm_box.pack_start(vm_label, False, False, 0) + + # Show all widgets (but container might be hidden) + self.infrastructure_box.show_all() + + def _on_infrastructure_toggle(self, button): + """Toggle infrastructure section visibility.""" + self.infrastructure_expanded = not self.infrastructure_expanded + + if self.infrastructure_expanded: + self.infrastructure_toggle.set_label("▼") + self.infrastructure_box.set_visible(True) + else: + self.infrastructure_toggle.set_label("▶") + self.infrastructure_box.set_visible(False) def filter_customers(self, entry): search_term = entry.get_text().strip() - + # Check for wildcard - show all customers if search_term == "*": self.filtered_customers = self.customers.copy() @@ -279,8 +561,12 @@ class VPNManagerWindow: # Check hosts and their services in this location def search_hosts(hosts): for host in hosts: + # Check IP addresses (search in any of the host's IPs) + ip_match = any(search_term_lower in host_ip.ip_address.lower( + ) for host_ip in host.ip_addresses) + if (search_term_lower in host.name.lower() or - search_term_lower in host.ip_address.lower() or + ip_match or search_term_lower in host.host_type.value.lower() or search_term_lower in host.description.lower()): return True @@ -307,11 +593,34 @@ class VPNManagerWindow: self.render_customers() def toggle_connection(self, 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.value}") + # Use actual VPN manager + if location.connected: + # Disconnect + self.log_view.log_info(f"Disconnecting from {location.name}...") + success = self.vpn_manager.disconnect_vpn(location) + if success: + location.connected = False + self.log_view.log_success(f"Disconnected from {location.name}") + else: + self.log_view.log_error( + f"Failed to disconnect from {location.name}") + else: + # Connect + self.log_view.log_info( + f"Connecting to {location.name} via {location.vpn_type.value}...") + success = self.vpn_manager.connect_vpn(location) + if success: + location.connected = True + self.log_view.log_success(f"Connected to {location.name}") + else: + self.log_view.log_error( + f"Failed to connect to {location.name}") + self.render_customers() + # Update VPN status after connection change + self.update_vpn_status() + def open_service(self, service): # Get the host IP from context - this would need to be passed properly in a real implementation print( @@ -343,6 +652,23 @@ class VPNManagerWindow: self.quit_app() return False + def update_vpn_status(self): + """Update location connection status from actual VPN manager.""" + if not self.vpn_manager: + return + + # Only update status for active locations to avoid unnecessary nmcli calls + for customer in self.customers: + for location in customer.locations: + if location.active: # Only check active locations + try: + status = self.vpn_manager.get_connection_status( + location) + location.connected = (status == VPNStatus.CONNECTED) + except VPNConnectionError: + # If we can't get status, assume disconnected + location.connected = False + def quit_app(self, _widget=None): # Stop the tray icon if hasattr(self, 'tray_icon'): diff --git a/models.py b/models.py index 8eed863..29d1551 100644 --- a/models.py +++ b/models.py @@ -40,12 +40,45 @@ class VPNType(Enum): IPSEC = "IPSec" +@dataclass +class NetworkSegment: + """Represents a network segment with metadata.""" + name: str # "LAN", "DMZ", "Management" + cidr: str # "192.168.1.0/24" + vlan_id: Optional[int] = None # VLAN 100 + zone: str = "general" # "production", "dmz", "management", "guest" + gateway: Optional[str] = None # "192.168.1.1" + description: str = "" # "Main office network" + + +@dataclass +class PortForwarding: + """Represents a port forwarding rule for external access.""" + external_port: int # Port on external address (e.g., 8080) + # Target internal IP (e.g., "192.168.1.10") + internal_ip: str + internal_port: int # Target internal port (e.g., 80) + protocol: str = "tcp" # "tcp", "udp", or "both" + description: str = "" # "Web server access" + enabled: bool = True # Whether the forwarding is active + + +@dataclass +class HostIP: + """IP address with network segment context.""" + ip_address: str + network_segment: str # References NetworkSegment.name + is_primary: bool = False # Primary interface for this host + + @dataclass class Host: """Represents a physical or virtual host at a location.""" name: str - ip_address: str - host_type: HostType + ip_addresses: List[HostIP] = field(default_factory=list) + host_type: HostType = HostType.LINUX + # Icon name without extension (e.g., 'ubuntu', 'windows') + icon: Optional[str] = None description: str = "" services: List[Service] = field(default_factory=list) sub_hosts: List['Host'] = field( @@ -62,6 +95,38 @@ class Host: """Check if this host has sub-hosts (VMs).""" return len(self.sub_hosts) > 0 + def get_primary_ip(self) -> str: + """Get the primary IP address, or first IP if no primary set.""" + if not self.ip_addresses: + return "" + + # Look for explicitly marked primary + for host_ip in self.ip_addresses: + if host_ip.is_primary: + return host_ip.ip_address + + # Fall back to first IP + return self.ip_addresses[0].ip_address + + def get_ip_display(self) -> str: + """Get a display string for IP addresses.""" + if not self.ip_addresses: + return "No IP" + elif len(self.ip_addresses) == 1: + return self.ip_addresses[0].ip_address + else: + primary_ip = self.get_primary_ip() + return f"{primary_ip} (+{len(self.ip_addresses)-1} more)" + + def get_all_ips(self) -> List[str]: + """Get all IP addresses as a simple list.""" + return [host_ip.ip_address for host_ip in self.ip_addresses] + + def get_ips_in_segment(self, segment_name: str) -> List[str]: + """Get all IP addresses in a specific network segment.""" + return [host_ip.ip_address for host_ip in self.ip_addresses + if host_ip.network_segment == segment_name] + @dataclass class Location: @@ -72,11 +137,24 @@ class Location: active: bool = False vpn_config: str = "" # Path to VPN config or connection details hosts: List[Host] = field(default_factory=list) - + + # Enhanced network configuration + network_segments: List[NetworkSegment] = field( + default_factory=list) # Network segments with rich metadata + external_addresses: List[str] = field( + default_factory=list) # External VPN endpoints + port_forwardings: List[PortForwarding] = field( + default_factory=list) # Port forwarding rules + + # Legacy field for backward compatibility (will be deprecated) + # Simple network list (legacy) + networks: List[str] = field(default_factory=list) + # VPN connection management fields - nmcli_connection_name: Optional[str] = None # NetworkManager connection name + # NetworkManager connection name + nmcli_connection_name: Optional[str] = None auto_import: bool = True # Auto-import .ovpn file if not in NetworkManager - + # Credential storage - can be: # - Passbolt UUID string (for future use) # - Dict with 'username' and 'password' keys @@ -112,6 +190,109 @@ class Location: """Get all hosts that have sub-hosts (hypervisors).""" return [host for host in self.get_all_hosts_flat() if host.is_hypervisor()] + def get_segment_by_name(self, segment_name: str) -> Optional[NetworkSegment]: + """Get a network segment by its name.""" + return next((seg for seg in self.network_segments if seg.name == segment_name), None) + + def get_hosts_in_segment(self, segment_name: str) -> List[Host]: + """Get all hosts that have IPs in the specified network segment.""" + hosts = [] + for host in self.get_all_hosts_flat(): + if any(host_ip.network_segment == segment_name for host_ip in host.ip_addresses): + hosts.append(host) + return hosts + + def get_segments_by_zone(self, zone: str) -> List[NetworkSegment]: + """Get all network segments in a specific zone.""" + return [seg for seg in self.network_segments if seg.zone == zone] + + def get_port_forwardings_for_host(self, host_ip: str) -> List[PortForwarding]: + """Get all port forwardings targeting a specific host IP.""" + return [pf for pf in self.port_forwardings if pf.internal_ip == host_ip and pf.enabled] + + def get_externally_accessible_services(self) -> List[tuple]: + """Get all services accessible from external addresses via port forwarding. + + Returns list of tuples: (external_address, external_port, host, service, port_forwarding) + """ + accessible_services = [] + + for external_addr in self.external_addresses: + for port_forward in self.port_forwardings: + if not port_forward.enabled: + continue + + # Find the host that owns the target IP + target_host = None + target_service = None + + for host in self.get_all_hosts_flat(): + host_ips = [hip.ip_address for hip in host.ip_addresses] + if port_forward.internal_ip in host_ips: + target_host = host + + # Find matching service on this host + for service in host.services: + if service.port == port_forward.internal_port: + target_service = service + break + break + + if target_host: + accessible_services.append(( + external_addr, + port_forward.external_port, + target_host, + target_service, # May be None if no matching service defined + port_forward + )) + + return accessible_services + + def is_service_externally_accessible(self, host_ip: str, service_port: int) -> bool: + """Check if a specific service is accessible from external addresses.""" + for pf in self.port_forwardings: + if (pf.enabled and + pf.internal_ip == host_ip and + pf.internal_port == service_port): + return True + return False + + def is_service_reachable(self, host: 'Host', service: Service) -> bool: + """Check if a service is reachable (either via VPN connection or port forwarding). + + Returns True if: + - VPN is connected (all internal services become reachable) + - Service has a port forwarding rule enabled + """ + # If VPN is connected, all services are reachable + if self.connected: + return True + + # Check if service is externally accessible via port forwarding + for host_ip in host.ip_addresses: + if self.is_service_externally_accessible(host_ip.ip_address, service.port): + return True + + return False + + def get_external_url_for_service(self, host: 'Host', service: Service) -> Optional[str]: + """Get the external URL for a service if it has port forwarding. + + Returns the external URL (e.g., "https://vpn.example.com:8006") or None. + """ + for host_ip in host.ip_addresses: + for pf in self.port_forwardings: + if (pf.enabled and + pf.internal_ip == host_ip.ip_address and + pf.internal_port == service.port): + # Use first external address if available + if self.external_addresses: + protocol = "https" if service.port in [ + 443, 8006, 8080] else "http" + return f"{protocol}://{self.external_addresses[0]}:{pf.external_port}" + return None + @dataclass class CustomerService: diff --git a/services/__init__.py b/services/__init__.py index 003ea75..befb764 100644 --- a/services/__init__.py +++ b/services/__init__.py @@ -1,17 +1,8 @@ """Services package for VPN and password management.""" -from .vpn_manager import VPNManager, VPNConnectionError, VPNStatus, VPNConnection -from .passbolt_client import PassboltClient, PassboltError, PassboltCredential -from .connection_manager import ConnectionManager, ConnectionConfig - +from .vpn_manager import VPNManager, VPNConnectionError, VPNStatus __all__ = [ 'VPNManager', - 'VPNConnection', - 'VPNConnectionError', - 'VPNStatus', - 'PassboltClient', - 'PassboltCredential', - 'PassboltError', - 'ConnectionManager', - 'ConnectionConfig', -] \ No newline at end of file + 'VPNConnectionError', + 'VPNStatus' +] diff --git a/services/connection_manager.py b/services/connection_manager.py deleted file mode 100644 index 016e2de..0000000 --- a/services/connection_manager.py +++ /dev/null @@ -1,266 +0,0 @@ -"""High-level connection manager that integrates VPN and Passbolt.""" - -import logging -from typing import Optional, Dict, Any -from pathlib import Path -from dataclasses import dataclass - -from .vpn_manager import VPNManager, VPNStatus, VPNConnectionError -from .passbolt_client import PassboltClient, PassboltError - - -logger = logging.getLogger(__name__) - - -@dataclass -class ConnectionConfig: - """Configuration for a VPN connection.""" - name: str - vpn_config_path: str - nmcli_connection_name: Optional[str] = None - auto_import: bool = True # Auto-import .ovpn if not in NetworkManager - # Credentials can be: - # - Passbolt UUID string (for future implementation) - # - Dict with 'username' and 'password' keys - # - None if no credentials needed - vpn_credentials: Optional[dict | str] = None - - -class ConnectionManager: - """Manages VPN connections with Passbolt credential integration.""" - - def __init__(self, use_passbolt: bool = True): - """Initialize the connection manager. - - Args: - use_passbolt: Whether to use Passbolt for credentials - """ - self.vpn_manager = VPNManager() - self.passbolt_client = None - - if use_passbolt: - try: - self.passbolt_client = PassboltClient() - logger.info("Passbolt client initialized successfully") - except PassboltError as e: - logger.warning(f"Passbolt not available: {e}") - logger.info("Falling back to manual credential entry") - - def connect_location(self, config: ConnectionConfig, - username: Optional[str] = None, - password: Optional[str] = None) -> None: - """Connect to a VPN location. - - Args: - config: Connection configuration - username: Override username (if not using Passbolt) - password: Override password (if not using Passbolt) - """ - # Ensure connection exists in NetworkManager - connection_name = self._ensure_connection(config) - - # Get credentials - check overrides first, then config - if not username or not password: - creds_username, creds_password = self._get_credentials_from_config(config) - username = username or creds_username - password = password or creds_password - - if not username or not password: - logger.info(f"No credentials provided for {connection_name}") - # nmcli will prompt for credentials - - # Connect - try: - logger.info(f"Connecting to {connection_name}") - self.vpn_manager.connect(connection_name, username, password) - logger.info(f"Successfully connected to {connection_name}") - except VPNConnectionError as e: - logger.error(f"Failed to connect to {connection_name}: {e}") - raise - - def disconnect_location(self, config: ConnectionConfig) -> None: - """Disconnect from a VPN location. - - Args: - config: Connection configuration - """ - connection_name = config.nmcli_connection_name or config.name - - if not self.vpn_manager.connection_exists(connection_name): - logger.warning(f"Connection {connection_name} does not exist") - return - - try: - logger.info(f"Disconnecting from {connection_name}") - self.vpn_manager.disconnect(connection_name) - logger.info(f"Successfully disconnected from {connection_name}") - except VPNConnectionError as e: - logger.error(f"Failed to disconnect from {connection_name}: {e}") - raise - - def get_connection_status(self, config: ConnectionConfig) -> VPNStatus: - """Get the status of a VPN connection. - - Args: - config: Connection configuration - - Returns: - Current VPN status - """ - connection_name = config.nmcli_connection_name or config.name - - if not self.vpn_manager.connection_exists(connection_name): - return VPNStatus.DISCONNECTED - - return self.vpn_manager.get_status(connection_name) - - def _ensure_connection(self, config: ConnectionConfig) -> str: - """Ensure VPN connection exists in NetworkManager. - - Args: - config: Connection configuration - - Returns: - Name of the NetworkManager connection - """ - connection_name = config.nmcli_connection_name or config.name - - # Check if connection already exists - if self.vpn_manager.connection_exists(connection_name): - logger.debug(f"Connection {connection_name} already exists") - return connection_name - - # Import if auto_import is enabled and config file exists - if config.auto_import and config.vpn_config_path: - vpn_file = Path(config.vpn_config_path) - if vpn_file.exists(): - logger.info(f"Importing VPN configuration from {vpn_file}") - imported_name = self.vpn_manager.import_ovpn( - str(vpn_file), - connection_name - ) - logger.info(f"Imported connection as {imported_name}") - return imported_name - else: - raise VPNConnectionError( - f"VPN config file not found: {config.vpn_config_path}" - ) - - raise VPNConnectionError( - f"Connection {connection_name} does not exist and auto-import is disabled" - ) - - def _get_credentials_from_config(self, config: ConnectionConfig) -> tuple[Optional[str], Optional[str]]: - """Get credentials from the configuration. - - Args: - config: Connection configuration - - Returns: - Tuple of (username, password) or (None, None) - """ - if not config.vpn_credentials: - return None, None - - # If it's a dict with username/password - if isinstance(config.vpn_credentials, dict): - username = config.vpn_credentials.get('username') - password = config.vpn_credentials.get('password') - return username, password - - # If it's a string (Passbolt UUID for future use) - if isinstance(config.vpn_credentials, str): - # For now, try to use Passbolt if available - if self.passbolt_client: - try: - return self._get_passbolt_credentials(config.vpn_credentials) - except (PassboltError, ValueError) as e: - logger.warning(f"Failed to get Passbolt credentials: {e}") - else: - logger.warning(f"Passbolt UUID provided but Passbolt client not available") - - return None, None - - def _get_passbolt_credentials(self, resource_id: str) -> tuple[str, str]: - """Get credentials from Passbolt. - - Args: - resource_id: Passbolt resource UUID - - Returns: - Tuple of (username, password) - """ - if not self.passbolt_client: - raise ValueError("Passbolt client not initialized") - - try: - credential = self.passbolt_client.get_credential(resource_id) - - if not credential.username or not credential.password: - raise ValueError( - f"Incomplete credentials for resource {resource_id}") - - return credential.username, credential.password - - except PassboltError as e: - logger.error(f"Failed to get Passbolt credentials: {e}") - raise - - def validate_passbolt_resource(self, resource_id: str) -> bool: - """Validate that a Passbolt resource exists and has required fields. - - Args: - resource_id: Passbolt resource UUID - - Returns: - True if resource is valid for VPN use - """ - if not self.passbolt_client: - return False - - try: - credential = self.passbolt_client.get_credential(resource_id) - return bool(credential.username and credential.password) - except PassboltError: - return False - - def import_all_configs(self, configs: list[ConnectionConfig]) -> Dict[str, bool]: - """Import multiple VPN configurations. - - Args: - configs: List of connection configurations - - Returns: - Dictionary mapping connection names to success status - """ - results = {} - - for config in configs: - try: - connection_name = self._ensure_connection(config) - results[connection_name] = True - logger.info(f"Successfully imported {connection_name}") - except VPNConnectionError as e: - results[config.name] = False - logger.error(f"Failed to import {config.name}: {e}") - - return results - - def cleanup_connection(self, config: ConnectionConfig, - remove_from_nm: bool = False) -> None: - """Clean up a VPN connection. - - Args: - config: Connection configuration - remove_from_nm: Whether to remove from NetworkManager - """ - connection_name = config.nmcli_connection_name or config.name - - # Disconnect if connected - if self.get_connection_status(config) == VPNStatus.CONNECTED: - self.disconnect_location(config) - - # Remove from NetworkManager if requested - if remove_from_nm and self.vpn_manager.connection_exists(connection_name): - logger.info(f"Removing {connection_name} from NetworkManager") - self.vpn_manager.delete_connection(connection_name) diff --git a/services/passbolt_client.py b/services/passbolt_client.py deleted file mode 100644 index 3b948b4..0000000 --- a/services/passbolt_client.py +++ /dev/null @@ -1,369 +0,0 @@ -"""Passbolt CLI integration for secure credential management.""" - -import subprocess -import json -import os -from dataclasses import dataclass -from typing import Optional, List, Dict, Any -from enum import Enum -from pathlib import Path - - -class PassboltResourceType(Enum): - """Types of resources in Passbolt.""" - PASSWORD = "password" - PASSWORD_WITH_DESCRIPTION = "password-with-description" - PASSWORD_STRING = "password-string" - TOTP = "totp" - - -class PassboltError(Exception): - """Exception raised for Passbolt operations.""" - pass - - -@dataclass -class PassboltCredential: - """Represents credentials retrieved from Passbolt.""" - resource_id: str - name: str - username: Optional[str] = None - password: Optional[str] = None - uri: Optional[str] = None - description: Optional[str] = None - resource_type: PassboltResourceType = PassboltResourceType.PASSWORD - - -@dataclass -class PassboltResource: - """Represents a Passbolt resource.""" - id: str - name: str - username: Optional[str] = None - uri: Optional[str] = None - resource_type: str = "password" - folder_parent_id: Optional[str] = None - personal: bool = False - - -class PassboltClient: - """Client for interacting with Passbolt through the CLI.""" - - def __init__(self, passbolt_cli_path: str = "passbolt"): - """Initialize Passbolt client. - - Args: - passbolt_cli_path: Path to the passbolt CLI executable - """ - self.cli_path = passbolt_cli_path - self._check_cli_available() - self._check_authentication() - - def _check_cli_available(self) -> None: - """Check if Passbolt CLI is available.""" - try: - subprocess.run([self.cli_path, '--version'], - capture_output=True, check=True) - except (subprocess.CalledProcessError, FileNotFoundError): - raise PassboltError( - f"Passbolt CLI not found at '{self.cli_path}'. " - "Please install: https://github.com/passbolt/go-passbolt-cli" - ) - - def _check_authentication(self) -> None: - """Check if authenticated with Passbolt.""" - try: - # Try to list resources to check auth - self._run_passbolt(['list', '--json'], check=True) - except PassboltError: - raise PassboltError( - "Not authenticated with Passbolt. " - "Please run: passbolt auth login" - ) - - def _run_passbolt(self, args: List[str], check: bool = True) -> subprocess.CompletedProcess: - """Run a Passbolt CLI command. - - Args: - args: Command arguments - check: Whether to check return code - - Returns: - Completed process result - """ - try: - result = subprocess.run( - [self.cli_path] + args, - capture_output=True, - text=True, - check=check - ) - return result - except subprocess.CalledProcessError as e: - raise PassboltError(f"Passbolt command failed: {e.stderr}") - - def get_credential(self, resource_id: str) -> PassboltCredential: - """Get a credential by resource ID. - - Args: - resource_id: UUID of the Passbolt resource - - Returns: - PassboltCredential object with username and password - """ - # Get the full resource - result = self._run_passbolt(['get', '--id', resource_id, '--json']) - - try: - data = json.loads(result.stdout) - except json.JSONDecodeError: - raise PassboltError(f"Failed to parse Passbolt response") - - # Extract fields based on resource type - credential = PassboltCredential( - resource_id=resource_id, - name=data.get('name', ''), - username=data.get('username'), - password=data.get('password'), - uri=data.get('uri'), - description=data.get('description') - ) - - # Determine resource type - if 'resource_type' in data: - try: - credential.resource_type = PassboltResourceType( - data['resource_type']) - except ValueError: - pass # Keep default - - return credential - - def get_field(self, resource_id: str, field: str) -> str: - """Get a specific field from a resource. - - Args: - resource_id: UUID of the Passbolt resource - field: Field name (e.g., 'password', 'username', 'uri') - - Returns: - Field value as string - """ - result = self._run_passbolt( - ['get', '--id', resource_id, '--field', field]) - return result.stdout.strip() - - def get_password(self, resource_id: str) -> str: - """Get just the password for a resource. - - Args: - resource_id: UUID of the Passbolt resource - - Returns: - Password string - """ - return self.get_field(resource_id, 'password') - - def get_username(self, resource_id: str) -> str: - """Get just the username for a resource. - - Args: - resource_id: UUID of the Passbolt resource - - Returns: - Username string - """ - return self.get_field(resource_id, 'username') - - def list_resources(self, folder_id: Optional[str] = None, - search: Optional[str] = None) -> List[PassboltResource]: - """List available resources. - - Args: - folder_id: Optional folder ID to filter by - search: Optional search term - - Returns: - List of PassboltResource objects - """ - args = ['list', '--json'] - - if folder_id: - args.extend(['--folder', folder_id]) - if search: - args.extend(['--filter', search]) - - result = self._run_passbolt(args) - - try: - data = json.loads(result.stdout) - except json.JSONDecodeError: - return [] - - resources = [] - for item in data: - resources.append(PassboltResource( - id=item['id'], - name=item.get('name', ''), - username=item.get('username'), - uri=item.get('uri'), - resource_type=item.get('resource_type', 'password'), - folder_parent_id=item.get('folder_parent_id'), - personal=item.get('personal', False) - )) - - return resources - - def find_resource_by_name(self, name: str) -> Optional[PassboltResource]: - """Find a resource by name. - - Args: - name: Name of the resource to find - - Returns: - First matching PassboltResource or None - """ - resources = self.list_resources(search=name) - for resource in resources: - if resource.name == name: - return resource - return None - - def create_resource(self, name: str, username: str, password: str, - uri: Optional[str] = None, - description: Optional[str] = None, - folder_id: Optional[str] = None) -> str: - """Create a new password resource. - - Args: - name: Resource name - username: Username - password: Password - uri: Optional URI/URL - description: Optional description - folder_id: Optional folder to place resource in - - Returns: - ID of created resource - """ - args = ['create', 'resource', - '--name', name, - '--username', username, - '--password', password] - - if uri: - args.extend(['--uri', uri]) - if description: - args.extend(['--description', description]) - if folder_id: - args.extend(['--folder', folder_id]) - - result = self._run_passbolt(args) - - # Parse the ID from output - # Output format: "Resource created: " - for line in result.stdout.split('\n'): - if 'created' in line.lower() and ':' in line: - parts = line.split(':', 1) - if len(parts) == 2: - return parts[1].strip() - - raise PassboltError("Failed to parse created resource ID") - - def update_resource(self, resource_id: str, - name: Optional[str] = None, - username: Optional[str] = None, - password: Optional[str] = None, - uri: Optional[str] = None, - description: Optional[str] = None) -> None: - """Update an existing resource. - - Args: - resource_id: ID of resource to update - name: New name (optional) - username: New username (optional) - password: New password (optional) - uri: New URI (optional) - description: New description (optional) - """ - args = ['update', 'resource', '--id', resource_id] - - if name: - args.extend(['--name', name]) - if username: - args.extend(['--username', username]) - if password: - args.extend(['--password', password]) - if uri: - args.extend(['--uri', uri]) - if description: - args.extend(['--description', description]) - - self._run_passbolt(args) - - def delete_resource(self, resource_id: str) -> None: - """Delete a resource. - - Args: - resource_id: ID of resource to delete - """ - self._run_passbolt(['delete', 'resource', '--id', resource_id]) - - def share_resource(self, resource_id: str, user_id: str, - permission: str = "read") -> None: - """Share a resource with another user. - - Args: - resource_id: ID of resource to share - user_id: ID of user to share with - permission: Permission level ('read', 'update', 'owner') - """ - self._run_passbolt([ - 'share', 'resource', - '--id', resource_id, - '--user', user_id, - '--permission', permission - ]) - - def list_folders(self) -> List[Dict[str, Any]]: - """List all folders. - - Returns: - List of folder dictionaries - """ - result = self._run_passbolt(['list', 'folder', '--json']) - - try: - return json.loads(result.stdout) - except json.JSONDecodeError: - return [] - - def get_folder_by_name(self, name: str) -> Optional[Dict[str, Any]]: - """Find a folder by name. - - Args: - name: Folder name to search for - - Returns: - Folder dictionary or None - """ - folders = self.list_folders() - for folder in folders: - if folder.get('name') == name: - return folder - return None - - def validate_resource_id(self, resource_id: str) -> bool: - """Check if a resource ID exists and is accessible. - - Args: - resource_id: UUID of the resource - - Returns: - True if resource exists and is accessible - """ - try: - self._run_passbolt(['get', '--id', resource_id, '--field', 'name']) - return True - except PassboltError: - return False diff --git a/services/vpn_manager.py b/services/vpn_manager.py index fbd38f8..1bc3f74 100644 --- a/services/vpn_manager.py +++ b/services/vpn_manager.py @@ -1,13 +1,13 @@ -"""VPN connection management using NetworkManager (nmcli).""" +"""Enhanced VPN management with VPNTray naming and route control.""" import subprocess -import tempfile -import os import re +import logging from dataclasses import dataclass -from typing import Optional, Dict, List +from typing import Optional, List from enum import Enum from pathlib import Path +from models import Location class VPNStatus(Enum): @@ -26,286 +26,482 @@ class VPNConnectionError(Exception): @dataclass -class VPNConnection: - """Represents a NetworkManager VPN connection.""" +class VPNConnectionInfo: + """Information about a VPN connection.""" name: str uuid: str - type: str + vpntray_name: str # Our custom name with vpntray_ prefix + status: VPNStatus device: Optional[str] = None - state: VPNStatus = VPNStatus.UNKNOWN - vpn_type: Optional[str] = None # OpenVPN, WireGuard, etc. + routes: List[str] = None # List of routes added class VPNManager: - """Manages VPN connections through NetworkManager CLI (nmcli).""" + """Enhanced VPN manager with VPNTray naming and route management.""" + + VPNTRAY_PREFIX = "vpntray_" + VPN_CONFIG_DIR = Path.home() / ".vpntray" / "vpn" def __init__(self): - """Initialize VPN manager and check for nmcli availability.""" + """Initialize VPN manager.""" + self.logger = logging.getLogger(__name__) self._check_nmcli_available() + self._ensure_vpn_config_dir() def _check_nmcli_available(self) -> None: - """Check if nmcli is available on the system.""" + """Check if nmcli is available.""" try: subprocess.run(['nmcli', '--version'], capture_output=True, check=True) except (subprocess.CalledProcessError, FileNotFoundError): raise VPNConnectionError( - "nmcli is not available. Please install NetworkManager.") + "nmcli is not available. Install NetworkManager.") + + def _ensure_vpn_config_dir(self) -> None: + """Ensure VPN config directory exists.""" + self.VPN_CONFIG_DIR.mkdir(parents=True, exist_ok=True) + + def _run_nmcli(self, args: List[str], check: bool = True, timeout: int = 30) -> subprocess.CompletedProcess: + """Run nmcli command with logging and timeout.""" + command = ['nmcli'] + args + command_str = ' '.join(command) - def _run_nmcli(self, args: List[str], check: bool = True) -> subprocess.CompletedProcess: - """Run an nmcli command with error handling.""" try: result = subprocess.run( - ['nmcli'] + args, + command, capture_output=True, text=True, - check=check + check=check, + timeout=timeout # Add timeout to prevent hanging ) + + self.logger.debug(f"Command: {command_str}") + if result.stdout.strip(): + self.logger.debug(f"Output: {result.stdout.strip()}") + if result.stderr.strip(): + self.logger.warning(f"Stderr: {result.stderr.strip()}") + if result.returncode == 0: + self.logger.debug("Command completed successfully") + else: + self.logger.error( + f"Command exited with code: {result.returncode}") + return result + except subprocess.TimeoutExpired: + self.logger.error( + f"Command timed out after {timeout}s: {command_str}") + raise VPNConnectionError( + f"nmcli command timed out after {timeout} seconds") except subprocess.CalledProcessError as e: - raise VPNConnectionError(f"nmcli command failed: {e.stderr}") - - def import_ovpn(self, ovpn_path: str, connection_name: Optional[str] = None) -> str: - """Import an OpenVPN configuration file. - - Args: - ovpn_path: Path to the .ovpn configuration file - connection_name: Optional custom name for the connection - - Returns: - The name of the imported connection - """ - ovpn_file = Path(ovpn_path) - if not ovpn_file.exists(): + self.logger.debug(f"Failed command: {command_str}") + if e.stdout and e.stdout.strip(): + self.logger.debug(f"Output: {e.stdout.strip()}") + if e.stderr and e.stderr.strip(): + self.logger.error(f"Error: {e.stderr.strip()}") + error_details = e.stderr or str(e) raise VPNConnectionError( - f"OpenVPN config file not found: {ovpn_path}") + f"nmcli command failed (exit code {e.returncode}): {error_details}") - # Import the configuration - result = self._run_nmcli([ - 'connection', 'import', 'type', 'openvpn', 'file', str(ovpn_file) - ]) + def _get_vpntray_connection_name(self, config_filename: str) -> str: + """Generate VPNTray-specific connection name.""" + # Remove extension and sanitize + base_name = Path(config_filename).stem + sanitized = re.sub(r'[^a-zA-Z0-9_-]', '_', base_name) + return f"{self.VPNTRAY_PREFIX}{sanitized}" - # Extract connection name from output - # nmcli typically outputs: "Connection 'name' (uuid) successfully added." - match = re.search(r"Connection '([^']+)'", result.stdout) - if not match: - raise VPNConnectionError( - "Failed to parse imported connection name") - - imported_name = match.group(1) - - # Rename if custom name provided - if connection_name and connection_name != imported_name: - self._run_nmcli([ - 'connection', 'modify', imported_name, - 'connection.id', connection_name - ]) - return connection_name - - return imported_name - - def connect(self, connection_name: str, - username: Optional[str] = None, - password: Optional[str] = None) -> None: - """Connect to a VPN. - - Args: - connection_name: Name of the NetworkManager connection - username: Optional username for authentication - password: Optional password for authentication - """ - if username and password: - # Create temporary secrets file - with tempfile.NamedTemporaryFile(mode='w', delete=False) as f: - f.write(f"vpn.secrets.password:{password}\n") - if username: - f.write(f"vpn.data.username:{username}\n") - secrets_file = f.name - - try: - self._run_nmcli([ - 'connection', 'up', connection_name, - 'passwd-file', secrets_file - ]) - finally: - # Always clean up secrets file - os.unlink(secrets_file) - else: - # Connect without credentials (will prompt if needed) - self._run_nmcli(['connection', 'up', connection_name]) - - def disconnect(self, connection_name: str) -> None: - """Disconnect from a VPN. - - Args: - connection_name: Name of the NetworkManager connection - """ - self._run_nmcli(['connection', 'down', connection_name]) - - def get_status(self, connection_name: str) -> VPNStatus: - """Get the status of a VPN connection. - - Args: - connection_name: Name of the NetworkManager connection - - Returns: - Current status of the VPN connection - """ - result = self._run_nmcli( - ['connection', 'show', '--active'], - check=False - ) - - if connection_name in result.stdout: - # Parse the actual state - state_result = self._run_nmcli( - ['connection', 'show', connection_name], - check=False - ) - - if 'GENERAL.STATE:' in state_result.stdout: - if 'activated' in state_result.stdout: - return VPNStatus.CONNECTED - elif 'activating' in state_result.stdout: - return VPNStatus.CONNECTING - elif 'deactivating' in state_result.stdout: - return VPNStatus.DISCONNECTING - - return VPNStatus.DISCONNECTED - - def list_connections(self, vpn_only: bool = True) -> List[VPNConnection]: - """List all NetworkManager connections. - - Args: - vpn_only: If True, only return VPN connections - - Returns: - List of VPNConnection objects - """ - args = ['connection', 'show'] - if vpn_only: - args.extend(['--type', 'vpn']) - - result = self._run_nmcli(args, check=False) + def get_vpn_config_path(self, filename: str) -> Path: + """Get full path to VPN config file.""" + return self.VPN_CONFIG_DIR / filename + def list_vpntray_connections(self) -> List[VPNConnectionInfo]: + """List all VPNTray-managed connections.""" connections = [] - for line in result.stdout.strip().split('\n')[1:]: # Skip header - if not line: - continue - parts = line.split() - if len(parts) >= 4: - name = parts[0] - uuid = parts[1] - conn_type = parts[2] - device = parts[3] if parts[3] != '--' else None + try: + result = self._run_nmcli(['connection', 'show']) + for line in result.stdout.strip().split('\\n'): + if self.VPNTRAY_PREFIX in line: + parts = line.split() + if len(parts) >= 4: + name = parts[0] + uuid = parts[1] + device = parts[3] if parts[3] != '--' else None - # Get current status - status = self.get_status(name) + # Get detailed status + status = self._get_connection_status(name) - connections.append(VPNConnection( - name=name, - uuid=uuid, - type=conn_type, - device=device, - state=status - )) + connections.append(VPNConnectionInfo( + name=name, + uuid=uuid, + vpntray_name=name, + status=status, + device=device + )) + except VPNConnectionError: + pass # No connections or nmcli error return connections - def delete_connection(self, connection_name: str) -> None: - """Delete a NetworkManager connection. + def _get_connection_status(self, connection_name: str) -> VPNStatus: + """Get the status of a specific connection.""" + try: + result = self._run_nmcli(['connection', 'show', connection_name]) - Args: - connection_name: Name of the connection to delete - """ - self._run_nmcli(['connection', 'delete', connection_name]) + # Parse connection state from output + for line in result.stdout.split('\\n'): + if 'GENERAL.STATE:' in line: + state = line.split(':')[1].strip() + if 'activated' in state.lower(): + return VPNStatus.CONNECTED + elif 'activating' in state.lower(): + return VPNStatus.CONNECTING + elif 'deactivating' in state.lower(): + return VPNStatus.DISCONNECTING + else: + return VPNStatus.DISCONNECTED + except VPNConnectionError: + pass - def connection_exists(self, connection_name: str) -> bool: - """Check if a connection exists. + return VPNStatus.UNKNOWN - Args: - connection_name: Name of the connection to check + def import_vpn_config(self, location: Location) -> str: + """Import VPN configuration for a location with VPNTray naming.""" + config_path = self.get_vpn_config_path(location.vpn_config) - Returns: - True if the connection exists - """ - result = self._run_nmcli( - ['connection', 'show', connection_name], - check=False - ) - return result.returncode == 0 + if not config_path.exists(): + raise VPNConnectionError(f"VPN config not found: {config_path}") - def modify_connection(self, connection_name: str, - settings: Dict[str, str]) -> None: - """Modify connection settings. + self.logger.info( + f"Config file exists: {config_path} ({config_path.stat().st_size} bytes)") - Args: - connection_name: Name of the connection to modify - settings: Dictionary of setting key-value pairs - e.g., {'vpn.data.comp-lzo': 'yes'} - """ - for key, value in settings.items(): + vpntray_name = self._get_vpntray_connection_name(location.vpn_config) + + # Check if already imported + if self._get_connection_by_name(vpntray_name): + self.logger.info(f"Connection already imported: {vpntray_name}") + return vpntray_name + + # Import based on VPN type + self.logger.info( + f"Importing {location.vpn_type.value} config: {config_path.name}") + + if location.vpn_type.value == "OpenVPN": + return self._import_openvpn(config_path, vpntray_name, location) + elif location.vpn_type.value == "WireGuard": + return self._import_wireguard(config_path, vpntray_name, location) + else: + raise VPNConnectionError( + f"Unsupported VPN type: {location.vpn_type.value}") + + def _import_openvpn(self, config_path: Path, vpntray_name: str, location: Location) -> str: + """Import OpenVPN configuration with route control.""" + # Import the config file first (nmcli will auto-generate a name) + import_args = [ + 'connection', 'import', 'type', 'openvpn', + 'file', str(config_path) + ] + self.logger.info(f"Running nmcli import: {' '.join(import_args)}") + + try: + result = self._run_nmcli(import_args) + + # Extract the auto-generated connection name from the output + # nmcli outputs: "Connection 'name' (uuid) successfully added." + import re + match = re.search(r"Connection '([^']+)'", result.stdout) + if not match: + raise VPNConnectionError( + "Failed to parse imported connection name from nmcli output") + + auto_generated_name = match.group(1) + self.logger.info( + f"Config imported with auto name: {auto_generated_name}") + + # Rename to our VPNTray naming convention + rename_args = [ + 'connection', 'modify', auto_generated_name, + 'connection.id', vpntray_name + ] + self.logger.info(f"Renaming to: {vpntray_name}") + + self._run_nmcli(rename_args) + self.logger.info( + f"OpenVPN config imported as {vpntray_name}") + + except VPNConnectionError as e: + self.logger.error(f"OpenVPN import failed: {e}") + raise + + # Configure credentials immediately after import if provided + if location.vpn_credentials: + self._configure_credentials(vpntray_name, location) + + # Configure the connection to not route everything by default + self._configure_connection_routes(vpntray_name, location) + + return vpntray_name + + def _import_wireguard(self, config_path: Path, vpntray_name: str, location: Location) -> str: + """Import WireGuard configuration with route control.""" + # Import the config file first (nmcli will auto-generate a name) + import_args = [ + 'connection', 'import', 'type', 'wireguard', + 'file', str(config_path) + ] + self.logger.info( + f"Running nmcli import: {' '.join(import_args)}") + + try: + result = self._run_nmcli(import_args) + + # Extract the auto-generated connection name from the output + # nmcli outputs: "Connection 'name' (uuid) successfully added." + import re + match = re.search(r"Connection '([^']+)'", result.stdout) + if not match: + raise VPNConnectionError( + "Failed to parse imported connection name from nmcli output") + + auto_generated_name = match.group(1) + self.logger.info( + f"Config imported with auto name: {auto_generated_name}") + + # Rename to our VPNTray naming convention + rename_args = [ + 'connection', 'modify', auto_generated_name, + 'connection.id', vpntray_name + ] + self.logger.info(f"Renaming to: {vpntray_name}") + + self._run_nmcli(rename_args) + self.logger.info( + f"WireGuard config imported as {vpntray_name}") + + except VPNConnectionError as e: + self.logger.error(f"WireGuard import failed: {e}") + raise + + # Configure credentials immediately after import if provided + if location.vpn_credentials: + self._configure_credentials(vpntray_name, location) + + # Configure routes + self._configure_connection_routes(vpntray_name, location) + + return vpntray_name + + def _configure_connection_routes(self, connection_name: str, location: Location) -> None: + """Configure connection to only route specified network segments.""" + try: + # Disable automatic default route self._run_nmcli([ 'connection', 'modify', connection_name, - key, value + 'ipv4.never-default', 'true' ]) - def get_connection_details(self, connection_name: str) -> Dict[str, str]: - """Get detailed information about a connection. + # Add routes for each network segment + routes = [] + for segment in location.network_segments: + # Add route for the network segment + routes.append(segment.cidr) - Args: - connection_name: Name of the connection + if routes: + routes_str = ','.join(routes) + self._run_nmcli([ + 'connection', 'modify', connection_name, + 'ipv4.routes', routes_str + ]) + self.logger.info( + f"Configured routes for {connection_name}: {routes_str}") - Returns: - Dictionary of connection properties - """ - result = self._run_nmcli(['connection', 'show', connection_name]) + except VPNConnectionError as e: + self.logger.error(f"Failed to configure routes: {e}") + # Don't fail the import, just log the error - details = {} - for line in result.stdout.strip().split('\n'): - if ':' in line: - key, value = line.split(':', 1) - details[key.strip()] = value.strip() - - return details - - def get_active_vpn_interface(self, connection_name: str) -> Optional[str]: - """Get the network interface used by an active VPN connection. - - Args: - connection_name: Name of the VPN connection - - Returns: - Interface name (e.g., 'tun0') or None if not connected - """ - if self.get_status(connection_name) != VPNStatus.CONNECTED: + def _get_connection_by_name(self, name: str) -> Optional[VPNConnectionInfo]: + """Get connection info by name.""" + try: + # Check if connection exists (simple and fast) + result = self._run_nmcli(['connection', 'show', name], check=False) + if result.returncode == 0: + # Connection exists, create minimal info object + return VPNConnectionInfo( + name=name, + uuid="unknown", + vpntray_name=name, + status=VPNStatus.UNKNOWN # Status will be checked when needed + ) + return None + except VPNConnectionError: return None - details = self.get_connection_details(connection_name) - return details.get('GENERAL.DEVICES') + def connect_vpn(self, location: Location) -> bool: + """Connect to VPN for a location.""" + try: + vpntray_name = self._get_vpntray_connection_name( + location.vpn_config) + config_path = self.get_vpn_config_path(location.vpn_config) + self.logger.info(f"VPN config: {config_path}") + self.logger.info(f"Connection name: {vpntray_name}") - def get_vpn_ip_address(self, connection_name: str) -> Optional[str]: - """Get the IP address assigned to the VPN connection. + # Check if config file exists + if not config_path.exists(): + error_msg = f"VPN config file not found: {config_path}" + self.logger.error(error_msg) + return False - Args: - connection_name: Name of the VPN connection + # Import if not already imported + existing_conn = self._get_connection_by_name(vpntray_name) + if not existing_conn: + self.logger.info( + "Importing VPN config for first time...") + try: + self.import_vpn_config(location) + self.logger.info( + "VPN config imported successfully") + except Exception as import_error: + error_msg = f"Failed to import VPN config: {import_error}" + self.logger.error(error_msg) + return False + else: + self.logger.info( + f"Using existing connection: {existing_conn.status.value}") - Returns: - IP address or None if not connected - """ - interface = self.get_active_vpn_interface(connection_name) - if not interface: - return None + # Connect with simple command - credentials already set during import + self.logger.info("Attempting connection...") - result = self._run_nmcli(['device', 'show', interface], check=False) + # Simple connection command without credential complications + connect_args = ['connection', 'up', vpntray_name] + self._run_nmcli(connect_args, timeout=60) + self.logger.info(f"Connected to {vpntray_name}") - for line in result.stdout.split('\n'): - if 'IP4.ADDRESS' in line and 'IP4.ADDRESS[2]' not in line: - # Format is usually "IP4.ADDRESS[1]: 10.0.0.1/24" - if ':' in line: - addr_part = line.split(':', 1)[1].strip() - if '/' in addr_part: - return addr_part.split('/')[0] + return True - return None + except VPNConnectionError as e: + self.logger.error(f"VPN connection failed: {e}") + return False + except Exception as e: + self.logger.error( + f"Unexpected error during connection: {e}") + return False + + def disconnect_vpn(self, location: Location) -> bool: + """Disconnect VPN for a location.""" + try: + vpntray_name = self._get_vpntray_connection_name( + location.vpn_config) + self.logger.info(f"Disconnecting from {vpntray_name}...") + + # Check if connection exists + existing_conn = self._get_connection_by_name(vpntray_name) + if not existing_conn: + self.logger.error( + f"Connection {vpntray_name} not found") + return False + + # Disconnect + self._run_nmcli(['connection', 'down', vpntray_name]) + self.logger.info(f"Disconnected from {vpntray_name}") + + return True + + except VPNConnectionError as e: + self.logger.error(f"Failed to disconnect: {e}") + return False + except Exception as e: + self.logger.error( + f"Unexpected error during disconnection: {e}") + return False + + def get_connection_status(self, location: Location) -> VPNStatus: + """Get connection status for a location.""" + vpntray_name = self._get_vpntray_connection_name(location.vpn_config) + return self._get_connection_status(vpntray_name) + + def remove_vpn_config(self, location: Location) -> bool: + """Remove VPN connection configuration.""" + try: + vpntray_name = self._get_vpntray_connection_name( + location.vpn_config) + + # First disconnect if connected + try: + self._run_nmcli( + ['connection', 'down', vpntray_name], check=False) + except VPNConnectionError: + pass # Ignore if already disconnected + + # Remove the connection + self._run_nmcli(['connection', 'delete', vpntray_name]) + self.logger.info( + f"Removed VPN configuration {vpntray_name}") + + return True + + except VPNConnectionError as e: + self.logger.error(f"Failed to remove config: {e}") + return False + + def cleanup_vpntray_connections(self) -> int: + """Remove all VPNTray-managed connections. Returns count removed.""" + connections = self.list_vpntray_connections() + removed_count = 0 + + for conn in connections: + try: + # Disconnect first + self._run_nmcli(['connection', 'down', conn.name], check=False) + # Remove + self._run_nmcli(['connection', 'delete', conn.name]) + removed_count += 1 + except VPNConnectionError: + pass # Continue with other connections + + if self.logger and removed_count > 0: + self.logger.info( + f"Cleaned up {removed_count} VPNTray connections") + + return removed_count + + def _configure_credentials(self, connection_name: str, location: Location) -> None: + """Configure VPN credentials directly in the connection.""" + if not location.vpn_credentials: + self.logger.info( + f"No credentials provided for {connection_name}") + return + + try: + # Handle dictionary credentials (username/password) + if isinstance(location.vpn_credentials, dict): + username = location.vpn_credentials.get('username') + password = location.vpn_credentials.get('password') + self.logger.info( + f"Setting credentials for {connection_name}...") + + # Set username and password with correct nmcli syntax + if username: + self._run_nmcli([ + 'connection', 'modify', connection_name, + '+vpn.data', f'username={username}' + ]) + self.logger.info( + f"Username configured for {connection_name}") + + if password: + self._run_nmcli([ + 'connection', 'modify', connection_name, + '+vpn.secrets', f'password={password}' + ]) + self.logger.info( + f"Password configured for {connection_name}") + + if username and password: + self.logger.info( + f"Full credentials configured for {connection_name}") + elif username or password: + self.logger.info( + f"Partial credentials configured for {connection_name}") + + except VPNConnectionError as e: + self.logger.error(f"Failed to configure credentials: {e}") + # Don't fail the whole operation for credential issues diff --git a/style.css b/style.css new file mode 100644 index 0000000..71180be --- /dev/null +++ b/style.css @@ -0,0 +1,56 @@ +.card { + background: @theme_base_color; + border-radius: 8px; + border: 1px solid @borders; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + padding: 16px; + margin: 6px; +} + +.location-info { + background: linear-gradient(to bottom, alpha(@theme_selected_bg_color, 0.1), alpha(@theme_selected_bg_color, 0.05)); + border-radius: 8px; + border: 1px solid alpha(@theme_selected_bg_color, 0.3); + padding: 12px; + margin-bottom: 12px; +} + +.log-section { + background: @theme_base_color; + border-top: 1px solid @borders; + border-radius: 8px 8px 0 0; +} + +/* Material Icons font */ +.material-icons { + font-family: "Material Icons"; + font-weight: normal; + font-style: normal; + font-size: 18px; +} + +/* Service button color coding with Material Icons */ +.service-icon-accessible { + font-family: "Material Icons"; + color: #4caf50; + /* Green for accessible */ + font-size: 18px; +} + +.service-icon-inaccessible { + font-family: "Material Icons"; + color: #f44336; + /* Red for not accessible */ + font-size: 18px; +} + +.service-icon-accessible:hover { + color: #2e7d32; + /* Darker green on hover */ +} + +.service-icon-inaccessible:disabled { + color: #ef9a9a; + /* Lighter red when disabled */ + opacity: 0.6; +} \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..38185a9 --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1,5 @@ +"""Utility modules for VPN Manager.""" + +from .icon_loader import IconLoader + +__all__ = ['IconLoader'] \ No newline at end of file diff --git a/utils/icon_loader.py b/utils/icon_loader.py new file mode 100644 index 0000000..b928a5a --- /dev/null +++ b/utils/icon_loader.py @@ -0,0 +1,142 @@ +"""Icon loader utility for host icons with fallback support.""" + +import os +from pathlib import Path +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GdkPixbuf +from models import HostType + + +class IconLoader: + """Manages loading of host icons with fallback to Material Icons.""" + + # Default icon size + ICON_SIZE = 20 + + # Project root directory + PROJECT_ROOT = Path(__file__).parent.parent + ICONS_DIR = PROJECT_ROOT / "assets" / "icons" + + # Material Icons fallback mapping for host types + HOST_TYPE_ICONS = { + HostType.LINUX: "computer", + HostType.WINDOWS: "desktop_windows", + HostType.WINDOWS_SERVER: "dns", + HostType.PROXMOX: "developer_board", + HostType.ESXI: "developer_board", + HostType.ROUTER: "router", + HostType.SWITCH: "device_hub" + } + + @classmethod + def get_host_icon_widget(cls, host, size=None) -> Gtk.Widget: + """Get an icon widget for a host, either custom SVG or Material Icon fallback. + + Args: + host: Host object with optional icon field + size: Icon size in pixels (default: ICON_SIZE) + + Returns: + Gtk.Image if custom icon exists, Gtk.Label with Material Icon otherwise + """ + if size is None: + size = cls.ICON_SIZE + + # Try custom icon first + if host.icon: + icon_widget = cls._load_custom_icon(host.icon, size) + if icon_widget: + return icon_widget + + # Fallback to Material Icons based on host type + return cls._create_material_icon(host.host_type, size) + + @classmethod + def _load_custom_icon(cls, icon_name: str, size: int) -> Gtk.Image: + """Load a custom SVG icon from assets/icons directory. + + Args: + icon_name: Name of the icon file without extension (e.g., 'ubuntu') + size: Icon size in pixels + + Returns: + Gtk.Image if icon exists, None otherwise + """ + # Try SVG first, then PNG + for extension in ['.svg', '.png']: + icon_path = cls.ICONS_DIR / f"{icon_name}{extension}" + + if icon_path.exists(): + try: + # Load and scale the icon + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( + str(icon_path), size, size + ) + image = Gtk.Image.new_from_pixbuf(pixbuf) + return image + except Exception as e: + print(f"Failed to load icon {icon_path}: {e}") + + return None + + @classmethod + def _create_material_icon(cls, host_type: HostType, size: int) -> Gtk.Label: + """Create a Material Icon label for a host type. + + Args: + host_type: HostType enum value + size: Icon size in pixels + + Returns: + Gtk.Label with Material Icon + """ + icon_name = cls.HOST_TYPE_ICONS.get(host_type, "computer") + + label = Gtk.Label() + label.set_text(icon_name) + label.get_style_context().add_class("material-icons") + + # Apply custom CSS for size + css = f""" + #{label.get_name()} {{ + font-size: {size}px; + }} + """ + + return label + + @classmethod + def get_service_icon(cls, service_type: str, is_accessible: bool) -> Gtk.Label: + """Get a Material Icon for a service with color coding. + + Args: + service_type: Service type string (e.g., 'SSH', 'Web GUI') + is_accessible: Whether the service is currently accessible + + Returns: + Gtk.Label with colored Material Icon + """ + # Service type to Material Icons mapping + service_icons = { + 'SSH': 'terminal', + 'Web GUI': 'language', + 'RDP': 'desktop_windows', + 'VNC': 'monitor', + 'SMB': 'folder_shared', + 'Database': 'storage', + 'FTP': 'cloud_upload' + } + + icon_name = service_icons.get(service_type, 'settings') + + label = Gtk.Label() + label.set_text(icon_name) + + # Apply color based on accessibility + if is_accessible: + label.get_style_context().add_class("service-icon-accessible") + else: + label.get_style_context().add_class("service-icon-inaccessible") + + return label \ No newline at end of file diff --git a/views/__init__.py b/views/__init__.py index 30d1617..502e8be 100644 --- a/views/__init__.py +++ b/views/__init__.py @@ -1,4 +1,5 @@ from .active_view import ActiveView from .inactive_view import InactiveView +from .log_view import LogView, LogLevel -__all__ = ['ActiveView', 'InactiveView'] \ No newline at end of file +__all__ = ['ActiveView', 'InactiveView', 'LogView', 'LogLevel'] \ No newline at end of file diff --git a/views/active_view.py b/views/active_view.py index 20f2778..b00f09d 100644 --- a/views/active_view.py +++ b/views/active_view.py @@ -1,62 +1,65 @@ +from widgets import ActiveCustomerCard +from gi.repository import Gtk import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk -from widgets import ActiveCustomerCard class ActiveView: """View for displaying active customer locations.""" - + def __init__(self, callbacks): self.callbacks = callbacks self.widget = self._create_widget() - + def _create_widget(self): """Create the main container for active locations.""" # Main container vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) - + # Scrolled window for content scrolled = Gtk.ScrolledWindow() scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) scrolled.set_shadow_type(Gtk.ShadowType.NONE) vbox.pack_start(scrolled, True, True, 0) - + # Content box - self.content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) + self.content_box = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=12) scrolled.add(self.content_box) - + return vbox - + def update(self, customers): """Update the view with new customer data. - + Args: customers: List of Customer objects with active locations to display """ # Clear existing content for child in self.content_box.get_children(): child.destroy() - + if customers: # Add customer cards for customer in customers: customer_card = ActiveCustomerCard(customer, self.callbacks) - self.content_box.pack_start(customer_card.widget, False, False, 0) + self.content_box.pack_start( + customer_card.widget, False, False, 0) else: # Show empty state message no_active_label = Gtk.Label() - no_active_label.set_markup("No active locations") + no_active_label.set_markup( + "No active locations") no_active_label.set_margin_top(20) self.content_box.pack_start(no_active_label, False, False, 0) - + self.content_box.show_all() - + def set_visible(self, visible): """Set visibility of the entire view.""" self.widget.set_visible(visible) - + def clear(self): """Clear all content from the view.""" for child in self.content_box.get_children(): - child.destroy() \ No newline at end of file + child.destroy() diff --git a/views/inactive_view.py b/views/inactive_view.py index 34845b3..131e162 100644 --- a/views/inactive_view.py +++ b/views/inactive_view.py @@ -1,52 +1,54 @@ +from widgets import InactiveCustomerCard +from gi.repository import Gtk import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk -from widgets import InactiveCustomerCard class InactiveView: """View for displaying inactive customer locations (search results).""" - + def __init__(self, callbacks): self.callbacks = callbacks self.widget = self._create_widget() self.current_search = "" - + def _create_widget(self): """Create the main container for inactive/search results.""" # Main container vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) - + # Scrolled window for content scrolled = Gtk.ScrolledWindow() scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) scrolled.set_shadow_type(Gtk.ShadowType.NONE) vbox.pack_start(scrolled, True, True, 0) - + # Content box - self.content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) + self.content_box = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=12) scrolled.add(self.content_box) - + return vbox - + def update(self, customers, search_term=""): """Update the view with search results. - + Args: customers: List of Customer objects with inactive locations to display search_term: The current search term """ self.current_search = search_term - + # Clear existing content for child in self.content_box.get_children(): child.destroy() - + if customers: # Add customer cards for customer in customers: customer_card = InactiveCustomerCard(customer, self.callbacks) - self.content_box.pack_start(customer_card.widget, False, False, 0) + self.content_box.pack_start( + customer_card.widget, False, False, 0) else: # Show no results message if search_term: @@ -56,14 +58,14 @@ class InactiveView: ) no_results_label.set_margin_top(20) self.content_box.pack_start(no_results_label, False, False, 0) - + self.content_box.show_all() - + def set_visible(self, visible): """Set visibility of the entire view.""" self.widget.set_visible(visible) - + def clear(self): """Clear all content from the view.""" for child in self.content_box.get_children(): - child.destroy() \ No newline at end of file + child.destroy() diff --git a/views/log_view.py b/views/log_view.py new file mode 100644 index 0000000..06ce0ea --- /dev/null +++ b/views/log_view.py @@ -0,0 +1,275 @@ +"""Log view for displaying command output and system logs.""" + +from enum import Enum +from typing import Optional +import time +from gi.repository import Gtk, GLib, Pango +import gi +gi.require_version('Gtk', '3.0') + + +class LogLevel(Enum): + """Log levels for different types of messages.""" + DEBUG = "DEBUG" + INFO = "INFO" + WARNING = "WARNING" + ERROR = "ERROR" + COMMAND = "COMMAND" + + +class LogView: + """View for displaying logs and command output.""" + + def __init__(self): + self.widget = self._create_widget() + self.max_lines = 1000 # Maximum number of log lines to keep + self.auto_scroll = True + + def _create_widget(self): + """Create the main log view widget.""" + # Main container + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + + # Header with controls + header_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + header_box.set_margin_start(12) + header_box.set_margin_end(12) + header_box.set_margin_top(8) + header_box.set_margin_bottom(8) + vbox.pack_start(header_box, False, False, 0) + + # Log title + log_label = Gtk.Label() + log_label.set_markup("📋 Command Log") + log_label.set_halign(Gtk.Align.START) + header_box.pack_start(log_label, False, False, 0) + + # Spacer + spacer = Gtk.Box() + header_box.pack_start(spacer, True, True, 0) + + # Auto-scroll toggle + self.autoscroll_switch = Gtk.Switch() + self.autoscroll_switch.set_active(True) + self.autoscroll_switch.connect( + "notify::active", self._on_autoscroll_toggle) + header_box.pack_start(self.autoscroll_switch, False, False, 0) + + autoscroll_label = Gtk.Label() + autoscroll_label.set_text("Auto-scroll") + autoscroll_label.set_margin_start(4) + header_box.pack_start(autoscroll_label, False, False, 0) + + # Clear button + clear_btn = Gtk.Button(label="Clear") + clear_btn.connect("clicked", self._on_clear_clicked) + header_box.pack_start(clear_btn, False, False, 0) + + # Separator + separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) + vbox.pack_start(separator, False, False, 0) + + # Scrolled window for log content + scrolled = Gtk.ScrolledWindow() + scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + scrolled.set_min_content_height(150) + scrolled.set_max_content_height(400) + vbox.pack_start(scrolled, True, True, 0) + + # Text view for log content + self.text_view = Gtk.TextView() + self.text_view.set_editable(False) + self.text_view.set_cursor_visible(False) + self.text_view.set_wrap_mode(Gtk.WrapMode.WORD) + + # Set monospace font + font_desc = Pango.FontDescription("monospace 9") + self.text_view.modify_font(font_desc) + + scrolled.add(self.text_view) + + # Get text buffer and create tags for different log levels + self.text_buffer = self.text_view.get_buffer() + self._create_text_tags() + + # Store reference to scrolled window for auto-scrolling + self.scrolled_window = scrolled + + return vbox + + def _create_text_tags(self): + """Create text tags for different log levels.""" + # Command tag (bold, blue) + command_tag = self.text_buffer.create_tag("command") + command_tag.set_property("weight", Pango.Weight.BOLD) + command_tag.set_property("foreground", "#0066cc") + + # Info tag (default) + info_tag = self.text_buffer.create_tag("info") + + # Warning tag (orange) + warning_tag = self.text_buffer.create_tag("warning") + warning_tag.set_property("foreground", "#ff8800") + + # Error tag (red, bold) + error_tag = self.text_buffer.create_tag("error") + error_tag.set_property("foreground", "#cc0000") + error_tag.set_property("weight", Pango.Weight.BOLD) + + # Debug tag (gray) + debug_tag = self.text_buffer.create_tag("debug") + debug_tag.set_property("foreground", "#666666") + + # Timestamp tag (small, gray) + timestamp_tag = self.text_buffer.create_tag("timestamp") + timestamp_tag.set_property("foreground", "#888888") + timestamp_tag.set_property("size", 8 * Pango.SCALE) + + def _on_autoscroll_toggle(self, switch, gparam): + """Handle auto-scroll toggle.""" + self.auto_scroll = switch.get_active() + + def _on_clear_clicked(self, button): + """Clear the log content.""" + self.text_buffer.set_text("") + + def _auto_scroll_to_bottom(self): + """Scroll to bottom if auto-scroll is enabled.""" + if not self.auto_scroll: + return + + # Get the end iterator + end_iter = self.text_buffer.get_end_iter() + + # Create a mark at the end + mark = self.text_buffer.get_insert() + self.text_buffer.place_cursor(end_iter) + + # Scroll to the mark + self.text_view.scroll_mark_onscreen(mark) + + def _get_timestamp(self) -> str: + """Get current timestamp string.""" + return time.strftime("%H:%M:%S") + + def _trim_log_if_needed(self): + """Trim log to max_lines if exceeded.""" + line_count = self.text_buffer.get_line_count() + if line_count <= self.max_lines: + return + + # Calculate how many lines to remove (keep some buffer) + lines_to_remove = line_count - (self.max_lines - 100) + + # Get iterator at start + start_iter = self.text_buffer.get_start_iter() + + # Move to the line we want to keep + end_iter = self.text_buffer.get_iter_at_line(lines_to_remove) + + # Delete the old lines + self.text_buffer.delete(start_iter, end_iter) + + def log_message(self, message: str, level: LogLevel = LogLevel.INFO, + command: Optional[str] = None): + """Add a log message to the view. + + Args: + message: The message to log + level: The log level + command: Optional command that generated this message + """ + # Ensure we're on the main thread + GLib.idle_add(self._add_log_message, message, level, command) + + def _add_log_message(self, message: str, level: LogLevel, command: Optional[str]): + """Add log message to buffer (main thread only).""" + timestamp = self._get_timestamp() + + # Get end iterator + end_iter = self.text_buffer.get_end_iter() + + # Add timestamp + self.text_buffer.insert_with_tags_by_name( + end_iter, f"[{timestamp}] ", "timestamp" + ) + + # Add command if provided + if command: + end_iter = self.text_buffer.get_end_iter() + self.text_buffer.insert_with_tags_by_name( + end_iter, f"$ {command}\n", "command" + ) + + # Add the message with appropriate tag + end_iter = self.text_buffer.get_end_iter() + tag_name = level.value.lower() + self.text_buffer.insert_with_tags_by_name( + end_iter, f"{message}\n", tag_name + ) + + # Trim log if needed + self._trim_log_if_needed() + + # Auto-scroll to bottom + self._auto_scroll_to_bottom() + + return False # Remove from idle queue + + def log_command(self, command: str, output: str = "", error: str = "", + return_code: int = 0): + """Log a command execution with its output. + + Args: + command: The command that was executed + output: Standard output from the command + error: Standard error from the command + return_code: Command return code + """ + # Log the command + self.log_message("", LogLevel.COMMAND, command) + + # Log output if present + if output.strip(): + for line in output.strip().split('\n'): + self.log_message(line, LogLevel.INFO) + + # Log error if present + if error.strip(): + for line in error.strip().split('\n'): + self.log_message(f"ERROR: {line}", LogLevel.ERROR) + + # Log return code if non-zero + if return_code != 0: + self.log_message( + f"Command exited with code: {return_code}", LogLevel.ERROR) + elif return_code == 0 and (output.strip() or error.strip()): + self.log_message("Command completed successfully", LogLevel.INFO) + + def log_info(self, message: str): + """Log an info message.""" + self.log_message(message, LogLevel.INFO) + + def log_warning(self, message: str): + """Log a warning message.""" + self.log_message(message, LogLevel.WARNING) + + def log_error(self, message: str): + """Log an error message.""" + self.log_message(message, LogLevel.ERROR) + + def log_debug(self, message: str): + """Log a debug message.""" + self.log_message(message, LogLevel.DEBUG) + + def log_success(self, message: str): + """Log a success message.""" + self.log_message(f"✓ {message}", LogLevel.INFO) + + def set_visible(self, visible: bool): + """Set visibility of the entire view.""" + self.widget.set_visible(visible) + + def clear(self): + """Clear all log content.""" + self._on_clear_clicked(None) diff --git a/widgets/__init__.py b/widgets/__init__.py index 501cb30..0bb0533 100644 --- a/widgets/__init__.py +++ b/widgets/__init__.py @@ -1,11 +1,12 @@ from .host_item import HostItem from .location_card import ActiveLocationCard, InactiveLocationCard -from .customer_card import ActiveCustomerCard, InactiveCustomerCard +from .active_customer_card import ActiveCustomerCard +from .inactive_customer_card import InactiveCustomerCard __all__ = [ 'HostItem', - 'ActiveLocationCard', + 'ActiveLocationCard', 'InactiveLocationCard', 'ActiveCustomerCard', 'InactiveCustomerCard' -] \ No newline at end of file +] diff --git a/widgets/active_customer_card.py b/widgets/active_customer_card.py new file mode 100644 index 0000000..b068c58 --- /dev/null +++ b/widgets/active_customer_card.py @@ -0,0 +1,399 @@ +from utils import IconLoader +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 ActiveCustomerCard: + def __init__(self, customer, callbacks): + self.customer = customer + self.callbacks = callbacks + self.expanded = True # Start expanded by default + self.location_expanded = {} # Track expansion state of each location + # Initialize all locations as expanded + for location in self.customer.locations: + self.location_expanded[location.name] = True + self.widget = self._create_widget() + + def _create_widget(self): + # Customer card container + customer_frame = Gtk.Frame() + customer_frame.get_style_context().add_class("card") + customer_frame.set_shadow_type(Gtk.ShadowType.NONE) + + customer_vbox = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=4) + customer_frame.add(customer_vbox) + + # Customer header row + customer_row = self._create_customer_header() + customer_vbox.pack_start(customer_row, False, False, 0) + + # Content container (locations) + self.content_box = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=6) + self.content_box.set_margin_start(8) + self.content_box.set_margin_end(8) + self.content_box.set_margin_bottom(8) + customer_vbox.pack_start(self.content_box, False, False, 0) + + # Add location cards + for location in self.customer.locations: + location_card = self._create_location_card(location) + self.content_box.pack_start(location_card, False, False, 0) + + return customer_frame + + def _create_customer_header(self): + """Create the customer header row.""" + row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + row.set_margin_start(4) + row.set_margin_end(4) + row.set_margin_top(4) + row.set_margin_bottom(2) + + # Expand/collapse arrow + self.expand_button = Gtk.Button() + self.expand_button.set_relief(Gtk.ReliefStyle.NONE) + self.expand_button.set_can_focus(False) + self.expand_button.set_size_request(20, 20) + self._update_expand_button() + self.expand_button.connect("clicked", self._on_expand_toggle) + row.pack_start(self.expand_button, False, False, 0) + + # Customer name + customer_label = Gtk.Label() + escaped_name = escape_markup(self.customer.name) + customer_label.set_markup(f"{escaped_name}") + customer_label.set_halign(Gtk.Align.START) + row.pack_start(customer_label, True, True, 0) + + # Customer service icons (right side) + self._add_customer_service_icons(row) + + return row + + def _create_location_card(self, location): + """Create a location card (subcard within customer card).""" + # Location subcard + location_frame = Gtk.Frame() + location_frame.get_style_context().add_class("card") + location_frame.set_shadow_type(Gtk.ShadowType.NONE) + + location_vbox = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=2) + location_frame.add(location_vbox) + + # Location header row + location_row = self._create_location_header(location) + location_vbox.pack_start(location_row, False, False, 0) + + # Hosts container (collapsible) + location_key = location.name + hosts_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) + hosts_box.set_margin_start(8) + hosts_box.set_margin_end(4) + hosts_box.set_margin_bottom(4) + + # Add hosts for this location + if location.hosts: + for host in location.hosts: + host_row = self._create_host_row(host, location) + hosts_box.pack_start(host_row, False, False, 0) + + # Add sub-hosts (VMs) + if host.sub_hosts: + for vm in host.sub_hosts: + vm_row = self._create_host_row( + vm, location, is_vm=True) + hosts_box.pack_start(vm_row, False, False, 0) + + location_vbox.pack_start(hosts_box, False, False, 0) + + # Store reference to hosts_box for expand/collapse + setattr(location_frame, 'hosts_box', hosts_box) + setattr(location_frame, 'location', location) + + # Set initial visibility + expanded = self.location_expanded.get(location_key, True) + hosts_box.set_visible(expanded) + + return location_frame + + def _create_location_header(self, location): + """Create the location header row.""" + row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + row.set_margin_start(4) + row.set_margin_end(4) + row.set_margin_top(4) + row.set_margin_bottom(2) + + # Location expand/collapse arrow + expand_btn = Gtk.Button() + expand_btn.set_relief(Gtk.ReliefStyle.NONE) + expand_btn.set_can_focus(False) + expand_btn.set_size_request(20, 20) + + # Set initial arrow direction + expanded = self.location_expanded.get(location.name, True) + expand_btn.set_label("▼" if expanded else "▶") + + # Connect to toggle function + expand_btn.connect( + "clicked", lambda btn: self._toggle_location_expansion(location, btn)) + row.pack_start(expand_btn, False, False, 0) + + # Location info + location_info = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=0) + + # Location name + name_label = Gtk.Label() + escaped_location_name = escape_markup(location.name) + name_label.set_markup(f"{escaped_location_name}") + name_label.set_halign(Gtk.Align.START) + location_info.pack_start(name_label, False, False, 0) + + # External addresses (small text) + if location.external_addresses: + addr_text = ", ".join( + location.external_addresses[:2]) # Show first 2 + if len(location.external_addresses) > 2: + addr_text += f" (+{len(location.external_addresses) - 2} more)" + addr_label = Gtk.Label() + addr_label.set_markup(f"{addr_text}") + addr_label.set_halign(Gtk.Align.START) + location_info.pack_start(addr_label, False, False, 0) + + row.pack_start(location_info, True, True, 0) + + # VPN Status + status_label = Gtk.Label() + if location.connected: + status_label.set_markup( + "Connected") + else: + status_label.set_markup( + "Disconnected") + row.pack_start(status_label, False, False, 0) + + # Action icons + self._add_location_action_icons(row, location) + + return row + + def _create_host_row(self, host, location, is_vm=False): + """Create a host row with aligned IP addresses.""" + row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + margin = 16 if not is_vm else 32 # VMs are more indented + row.set_margin_start(margin) + row.set_margin_end(4) + row.set_margin_top(1) + row.set_margin_bottom(1) + + # Host icon - custom or fallback to Material Icons + icon_widget = IconLoader.get_host_icon_widget(host, size=20) + icon_container = Gtk.Box() + icon_container.set_size_request(24, 24) # Fixed size for alignment + icon_container.set_center_widget(icon_widget) + row.pack_start(icon_container, False, False, 0) + + # Host name (fixed width for alignment) + escaped_host_name = escape_markup(host.name) + name_markup = f"{escaped_host_name}" if is_vm else f"{escaped_host_name}" + name_label = Gtk.Label() + name_label.set_markup(name_markup) + name_label.set_halign(Gtk.Align.START) + name_label.set_size_request(150, -1) # Fixed width to align IPs + row.pack_start(name_label, False, False, 0) + + # IP address (aligned in middle) + ip_label = Gtk.Label() + ip_label.set_markup(f"{host.get_ip_display()}") + ip_label.set_size_request(120, -1) # Fixed width for alignment + ip_label.set_halign(Gtk.Align.CENTER) + ip_label.set_tooltip_text(", ".join(host.get_all_ips()) if len( + host.ip_addresses) > 1 else host.get_primary_ip()) + row.pack_start(ip_label, False, False, 0) + + # Spacer to push service icons to the right + spacer = Gtk.Box() + row.pack_start(spacer, True, True, 0) + + # Service action icons + self._add_host_service_icons(row, host, location) + + return row + + def _add_customer_service_icons(self, row): + """Add customer service icons to the right side.""" + # Service type to icon mapping + service_icons = { + 'Email & Office': '📧', # O365 + 'Phone System': '📞', # PBX + 'CRM': '👥', # Salesforce + 'Email': '📧', + 'Office': '📄', + } + + # Add icons for each service + for service in self.customer.services[:4]: # Limit to 4 icons + icon = service_icons.get(service.service_type, '🌐') + + btn = Gtk.Button() + btn.set_label(icon) + btn.set_relief(Gtk.ReliefStyle.NONE) + btn.set_can_focus(False) + btn.set_size_request(24, 24) + btn.set_tooltip_text(f"Open {service.name}") + btn.connect("clicked", lambda b, + s=service: self.callbacks['open_customer_service'](s)) + row.pack_start(btn, False, False, 0) + + # Menu button (always last) + menu_btn = self._create_menu_button() + row.pack_start(menu_btn, False, False, 0) + + def _add_location_action_icons(self, row, location): + """Add location action icons.""" + # Connection toggle + connect_icon = "🔌" if not location.connected else "🔓" + connect_btn = Gtk.Button() + connect_btn.set_label(connect_icon) + connect_btn.set_relief(Gtk.ReliefStyle.NONE) + connect_btn.set_can_focus(False) + connect_btn.set_size_request(24, 24) + tooltip = "Connect to VPN" if not location.connected else "Disconnect VPN" + connect_btn.set_tooltip_text(tooltip) + connect_btn.connect( + "clicked", lambda b: self.callbacks['toggle_connection'](location)) + row.pack_start(connect_btn, False, False, 0) + + # Refresh/reload + refresh_btn = Gtk.Button() + refresh_btn.set_label("🔄") + refresh_btn.set_relief(Gtk.ReliefStyle.NONE) + refresh_btn.set_can_focus(False) + refresh_btn.set_size_request(24, 24) + refresh_btn.set_tooltip_text("Refresh connection") + row.pack_start(refresh_btn, False, False, 0) + + # Menu + menu_btn = self._create_menu_button() + row.pack_start(menu_btn, False, False, 0) + + def _add_host_service_icons(self, row, host, location): + """Add host service icons with reachability check.""" + # Service type to Material Icons mapping + # Icon names from: https://fonts.google.com/icons + service_icons = { + 'SSH': 'terminal', # Terminal icon for SSH + 'Web GUI': 'language', # Globe icon for web + 'RDP': 'desktop_windows', # Desktop icon for RDP + 'VNC': 'monitor', # Monitor icon for VNC + 'SMB': 'folder_shared', # Shared folder for SMB + 'Database': 'storage', # Database/storage icon + 'FTP': 'cloud_upload' # Upload icon for FTP + } + + # Add icons for services + for service in host.services[:3]: # Limit to 3 service icons + # Default to settings icon + icon = service_icons.get(service.service_type.value, 'settings') + + # Check if service is reachable + is_reachable = location.is_service_reachable(host, service) + is_external = location.get_external_url_for_service( + host, service) is not None + + btn = Gtk.Button() + btn.set_label(icon) # Material Icons uses ligatures + btn.set_relief(Gtk.ReliefStyle.NONE) + btn.set_can_focus(False) + btn.set_size_request(24, 24) + + # Apply color styling based on reachability + if is_reachable: + # Green for accessible + btn.get_style_context().add_class("service-icon-accessible") + if is_external and not location.connected: + external_url = location.get_external_url_for_service( + host, service) + btn.set_tooltip_text( + f"{service.service_type.value}: {service.name}\nExternal: {external_url}") + else: + btn.set_tooltip_text( + f"{service.service_type.value}: {service.name}") + else: + # Red for not accessible + btn.get_style_context().add_class("service-icon-inaccessible") + btn.set_tooltip_text( + f"{service.service_type.value}: {service.name}\nNot reachable (VPN disconnected)") + + # Enable/disable based on reachability + btn.set_sensitive(is_reachable) + + # Connect click handler only if reachable + if is_reachable: + btn.connect("clicked", lambda b, + s=service: self.callbacks['open_service'](s)) + + row.pack_start(btn, False, False, 0) + + # Menu button + menu_btn = self._create_menu_button() + row.pack_start(menu_btn, False, False, 0) + + def _create_menu_button(self): + """Create a menu button with empty popup.""" + menu_btn = Gtk.MenuButton() + menu_btn.set_label("⋯") # Three dots menu + menu_btn.set_relief(Gtk.ReliefStyle.NONE) + menu_btn.set_can_focus(False) + menu_btn.set_size_request(24, 24) + + # Create empty menu for now + menu = Gtk.Menu() + placeholder_item = Gtk.MenuItem(label="(Empty menu)") + placeholder_item.set_sensitive(False) + menu.append(placeholder_item) + menu.show_all() + + menu_btn.set_popup(menu) + return menu_btn + + def _update_expand_button(self): + """Update the expand button arrow direction.""" + if self.expanded: + self.expand_button.set_label("▼") + else: + self.expand_button.set_label("▶") + + def _toggle_location_expansion(self, location, button): + """Toggle the expansion state of a specific location.""" + location_key = location.name + current_state = self.location_expanded.get(location_key, True) + new_state = not current_state + self.location_expanded[location_key] = new_state + + # Update button arrow + button.set_label("▼" if new_state else "▶") + + # Find the location card and toggle its hosts box visibility + for widget in self.content_box.get_children(): + if hasattr(widget, 'location') and widget.location.name == location_key: + hosts_box = getattr(widget, 'hosts_box', None) + if hosts_box: + hosts_box.set_visible(new_state) + break + + def _on_expand_toggle(self, button): + """Toggle the expanded state.""" + self.expanded = not self.expanded + self._update_expand_button() + self.content_box.set_visible(self.expanded) diff --git a/widgets/customer_card.py b/widgets/customer_card.py deleted file mode 100644 index 45c746b..0000000 --- a/widgets/customer_card.py +++ /dev/null @@ -1,114 +0,0 @@ -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk -from .location_card import ActiveLocationCard, InactiveLocationCard - - -class ActiveCustomerCard: - def __init__(self, customer, callbacks): - self.customer = customer - self.callbacks = callbacks - self.widget = self._create_widget() - - def _create_widget(self): - # GNOME-style card container - card_frame = Gtk.Frame() - card_frame.get_style_context().add_class("card") - card_frame.set_shadow_type(Gtk.ShadowType.NONE) # Shadow handled by CSS - - card_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) - card_frame.add(card_vbox) - - # Customer header - customer_label = Gtk.Label() - customer_label.set_markup(f"🏢 {self.customer.name}") - customer_label.set_halign(Gtk.Align.START) - card_vbox.pack_start(customer_label, False, False, 0) - - # Customer services section - if self.customer.services: - services_label = Gtk.Label() - services_label.set_markup("Cloud Services") - services_label.set_halign(Gtk.Align.START) - services_label.set_margin_top(8) - card_vbox.pack_start(services_label, False, False, 0) - - # Services box with indent - services_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) - services_box.set_margin_start(16) - services_box.set_margin_bottom(8) - card_vbox.pack_start(services_box, False, False, 0) - - for service in self.customer.services: - service_btn = Gtk.Button(label=service.name) - service_btn.get_style_context().add_class("suggested-action") - service_btn.connect("clicked", lambda btn, s=service: self.callbacks['open_customer_service'](s)) - services_box.pack_start(service_btn, False, False, 0) - - # Locations section - for i, location in enumerate(self.customer.locations): - if i > 0: # Add separator between locations - separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) - separator.set_margin_top(8) - separator.set_margin_bottom(8) - card_vbox.pack_start(separator, False, False, 0) - - location_card = ActiveLocationCard(location, self.customer.name, self.callbacks) - card_vbox.pack_start(location_card.widget, False, False, 0) - - return card_frame - - -class InactiveCustomerCard: - def __init__(self, customer, callbacks): - self.customer = customer - self.callbacks = callbacks - self.widget = self._create_widget() - - def _create_widget(self): - # GNOME-style card container - card_frame = Gtk.Frame() - card_frame.get_style_context().add_class("card") - card_frame.set_shadow_type(Gtk.ShadowType.NONE) # Shadow handled by CSS - - card_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) - card_frame.add(card_vbox) - - # Customer header - muted - customer_label = Gtk.Label() - customer_label.set_markup(f"🏢 {self.customer.name}") - customer_label.set_halign(Gtk.Align.START) - card_vbox.pack_start(customer_label, False, False, 0) - - # Customer services section - list format for inactive - if self.customer.services: - services_label = Gtk.Label() - services_label.set_markup("Cloud Services") - services_label.set_halign(Gtk.Align.START) - services_label.set_margin_top(8) - card_vbox.pack_start(services_label, False, False, 0) - - # Services list with indent - services_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) - services_vbox.set_margin_start(16) - services_vbox.set_margin_bottom(8) - card_vbox.pack_start(services_vbox, False, False, 0) - - for service in self.customer.services: - service_label = Gtk.Label() - service_label.set_markup(f"• {service.name} ({service.service_type})") - service_label.set_halign(Gtk.Align.START) - services_vbox.pack_start(service_label, False, False, 0) - - # Locations section - for i, location in enumerate(self.customer.locations): - if i > 0: # Add separator between locations - separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) - separator.set_margin_top(8) - separator.set_margin_bottom(8) - card_vbox.pack_start(separator, False, False, 0) - - location_card = InactiveLocationCard(location, self.customer.name, self.callbacks) - card_vbox.pack_start(location_card.widget, False, False, 0) - - return card_frame \ No newline at end of file diff --git a/widgets/host_item.py b/widgets/host_item.py index 980c7a9..8390420 100644 --- a/widgets/host_item.py +++ b/widgets/host_item.py @@ -2,11 +2,18 @@ import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from models import ServiceType, HostType +from utils import IconLoader + + +def escape_markup(text: str) -> str: + """Escape special characters for Pango markup.""" + return text.replace('&', '&').replace('<', '<').replace('>', '>') class HostItem: - def __init__(self, host, open_service_callback): + def __init__(self, host, location, open_service_callback): self.host = host + self.location = location self.open_service_callback = open_service_callback self.widget = self._create_widget() @@ -18,20 +25,12 @@ class HostItem: host_header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) host_box.pack_start(host_header, False, False, 0) - # Host type icon - type_icons = { - HostType.LINUX: "🐧", - HostType.WINDOWS: "🪟", - HostType.WINDOWS_SERVER: "🖥️", - HostType.PROXMOX: "📦", - HostType.ESXI: "📦", - HostType.ROUTER: "🌐", - HostType.SWITCH: "🔗" - } - icon = type_icons.get(self.host.host_type, "💻") - - icon_label = Gtk.Label(label=icon) - host_header.pack_start(icon_label, False, False, 0) + # Host icon - custom or fallback to Material Icons + icon_widget = IconLoader.get_host_icon_widget(self.host, size=24) + icon_container = Gtk.Box() + icon_container.set_size_request(32, 24) # Fixed size + icon_container.set_center_widget(icon_widget) + host_header.pack_start(icon_container, False, False, 0) # Host details - compact single line details_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1) @@ -39,8 +38,14 @@ class HostItem: # Host name with IP inline name_label = Gtk.Label() - name_label.set_markup(f"{self.host.name} ({self.host.host_type.value}) - {self.host.ip_address}") + ip_display = self.host.get_ip_display() + escaped_host_name = escape_markup(self.host.name) + escaped_host_type = escape_markup(self.host.host_type.value) + escaped_ip_display = escape_markup(ip_display) + name_label.set_markup(f"{escaped_host_name} ({escaped_host_type}) - {escaped_ip_display}") name_label.set_halign(Gtk.Align.START) + if len(self.host.ip_addresses) > 1: + name_label.set_tooltip_text(f"All IPs: {', '.join(self.host.get_all_ips())}") details_vbox.pack_start(name_label, False, False, 0) # Services section - compact button row @@ -52,9 +57,35 @@ class HostItem: for service in self.host.services: if service.service_type in [ServiceType.WEB_GUI, ServiceType.SSH, ServiceType.RDP]: # Only show launchable services + # Check if service is reachable + is_reachable = self.location.is_service_reachable(self.host, service) + is_external = self.location.get_external_url_for_service(self.host, service) is not None + service_btn = Gtk.Button(label=service.service_type.value) - service_btn.get_style_context().add_class("suggested-action") - service_btn.connect("clicked", lambda btn, s=service: self._on_service_clicked(s)) + + # Apply color-based styling + if is_reachable: + # Green styling for accessible services + service_btn.get_style_context().add_class("suggested-action") + service_btn.set_name("service-btn-accessible") + if is_external and not self.location.connected: + external_url = self.location.get_external_url_for_service(self.host, service) + service_btn.set_tooltip_text(f"Open {service.name}\nExternal: {external_url}") + else: + service_btn.set_tooltip_text(f"Open {service.name}") + else: + # Red styling for inaccessible services + service_btn.get_style_context().add_class("destructive-action") + service_btn.set_name("service-btn-inaccessible") + service_btn.set_tooltip_text(f"{service.name} - Not reachable (VPN disconnected)") + + # Enable/disable based on reachability + service_btn.set_sensitive(is_reachable) + + # Connect handler only if reachable + if is_reachable: + service_btn.connect("clicked", lambda btn, s=service: self._on_service_clicked(s)) + services_box.pack_start(service_btn, False, False, 0) # Sub-hosts (VMs) section @@ -71,7 +102,7 @@ class HostItem: host_box.pack_start(subhosts_box, False, False, 0) for subhost in self.host.sub_hosts: - subhost_item = HostItem(subhost, self.open_service_callback) + subhost_item = HostItem(subhost, self.location, self.open_service_callback) subhosts_box.pack_start(subhost_item.widget, False, False, 0) return host_box diff --git a/widgets/inactive_customer_card.py b/widgets/inactive_customer_card.py new file mode 100644 index 0000000..f452b11 --- /dev/null +++ b/widgets/inactive_customer_card.py @@ -0,0 +1,118 @@ +from .location_card import InactiveLocationCard +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 InactiveCustomerCard: + def __init__(self, customer, callbacks): + self.customer = customer + self.callbacks = callbacks + self.expanded = False # Start collapsed by default for inactive + self.widget = self._create_widget() + + def _create_widget(self): + # GNOME-style card container + card_frame = Gtk.Frame() + card_frame.get_style_context().add_class("card") + card_frame.set_shadow_type( + Gtk.ShadowType.NONE) # Shadow handled by CSS + + card_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) + card_frame.add(card_vbox) + + # Customer header with expand/collapse button - muted + header_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + card_vbox.pack_start(header_box, False, False, 0) + + # Expand/collapse arrow button + self.expand_button = Gtk.Button() + self.expand_button.set_relief(Gtk.ReliefStyle.NONE) + self.expand_button.set_can_focus(False) + self._update_expand_button() + self.expand_button.connect("clicked", self._on_expand_toggle) + header_box.pack_start(self.expand_button, False, False, 0) + + # Customer name - muted + customer_label = Gtk.Label() + escaped_name = escape_markup(self.customer.name) + customer_label.set_markup( + f"🏢 {escaped_name}") + customer_label.set_halign(Gtk.Align.START) + header_box.pack_start(customer_label, False, False, 0) + + # Location count badge + inactive_count = len(self.customer.locations) + if inactive_count > 0: + count_label = Gtk.Label() + count_label.set_markup( + f"({inactive_count})") + header_box.pack_start(count_label, False, False, 0) + + # Content container (collapsible) + self.content_box = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=12) + self.content_box.set_visible(self.expanded) # Start hidden + card_vbox.pack_start(self.content_box, False, False, 0) + + # Customer services section - list format for inactive + if self.customer.services: + services_label = Gtk.Label() + services_label.set_markup( + "Cloud Services") + services_label.set_halign(Gtk.Align.START) + services_label.set_margin_top(8) + self.content_box.pack_start(services_label, False, False, 0) + + # Services list with indent + services_vbox = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=2) + services_vbox.set_margin_start(16) + services_vbox.set_margin_bottom(8) + self.content_box.pack_start(services_vbox, False, False, 0) + + for service in self.customer.services: + service_label = Gtk.Label() + # Escape special characters in markup text + escaped_name = escape_markup(service.name) + escaped_type = escape_markup(service.service_type) + service_label.set_markup( + f"• {escaped_name} ({escaped_type})") + service_label.set_halign(Gtk.Align.START) + services_vbox.pack_start(service_label, False, False, 0) + + # Locations section + for i, location in enumerate(self.customer.locations): + if i > 0: # Add separator between locations + separator = Gtk.Separator( + orientation=Gtk.Orientation.HORIZONTAL) + separator.set_margin_top(8) + separator.set_margin_bottom(8) + self.content_box.pack_start(separator, False, False, 0) + + location_card = InactiveLocationCard( + location, self.customer.name, self.callbacks) + self.content_box.pack_start(location_card.widget, False, False, 0) + + # Show all content in the box (but box itself may be hidden) + self.content_box.show_all() + + return card_frame + + def _update_expand_button(self): + """Update the expand button arrow direction.""" + if self.expanded: + self.expand_button.set_label("▼") + else: + self.expand_button.set_label("▶") + + def _on_expand_toggle(self, button): + """Toggle the expanded state.""" + self.expanded = not self.expanded + self._update_expand_button() + self.content_box.set_visible(self.expanded) diff --git a/widgets/location_card.py b/widgets/location_card.py index 5f9167a..4b90389 100644 --- a/widgets/location_card.py +++ b/widgets/location_card.py @@ -1,8 +1,13 @@ +from models import VPNType +from .host_item import HostItem +from gi.repository import Gtk import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk -from .host_item import HostItem -from models import VPNType + + +def escape_markup(text: str) -> str: + """Escape special characters for Pango markup.""" + return text.replace('&', '&').replace('<', '<').replace('>', '>') class ActiveLocationCard: @@ -11,49 +16,78 @@ class ActiveLocationCard: 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_vbox = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=8) + # Location header with controls - header_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) + 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() - location_label.set_markup(f"📍 {self.location.name}") + 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 + + # 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"{vpn_icon} {self.location.vpn_type.value} VPN") + 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) + 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}") + 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) @@ -63,14 +97,14 @@ class ActiveLocationCard: 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() @@ -78,23 +112,26 @@ class ActiveLocationCard: 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 = 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.callbacks['open_service']) + 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) + self.callbacks['deactivate_location']( + self.location, self.customer_name) class InactiveLocationCard: @@ -103,54 +140,68 @@ class InactiveLocationCard: 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_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() - location_label.set_markup(f"📍 {self.location.name}") + 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 host count + + # VPN type, external address and host count vpn_icons = { VPNType.OPENVPN: "🔒", - VPNType.WIREGUARD: "⚡", + 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"{vpn_icon} {self.location.vpn_type.value} VPN • {host_count} hosts") + 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) - + 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) \ No newline at end of file + self.callbacks['set_current_location']( + self.location, self.customer_name)