@@ -17,7 +17,7 @@ import yaml
APP_TITLE = " PulseGate Desktop "
APP_VERSION = " 1.1.0 "
APP_VERSION = " 1.2.0 "
APP_URL = " https://git.pepe44.dev/Pepe44DEV/PulseGate-GUI "
IS_WINDOWS = sys . platform == " win32 "
CONNECT_TIMEOUT = 2.5
@@ -25,60 +25,64 @@ STATUS_OK = "#00e07a"
THEMES = {
" neon-green " : {
" bg " : " #07110d " ,
" sidebar " : " #050c09 " ,
" surface " : " #0d1b15 " ,
" surface_2 " : " #13251e " ,
" surface_3 " : " #172c24 " ,
" field " : " #09150f " ,
" line " : " #1f4b38 " ,
" text " : " #d7 ffe9 " ,
" muted " : " #8a a99b " ,
" bg " : " #050d09 " ,
" sidebar " : " #040b08 " ,
" surface " : " #0c1a13 " ,
" surface_2 " : " #12241b " ,
" surface_3 " : " #193127 " ,
" field " : " #08130e " ,
" line " : " #1d4a37 " ,
" text " : " #d9 ffe9 " ,
" muted " : " #86 a998 " ,
" accent " : " #00ff99 " ,
" accent_2 " : " #33c cff " ,
" danger " : " #ff6666 " ,
" accent_hi " : " #5 cffb9 " ,
" accent_2 " : " #3ecfff " ,
" danger " : " #ff7070 " ,
} ,
" ocean-blue " : {
" bg " : " #07 1018 " ,
" sidebar " : " #040b12 " ,
" surface " : " #0e1b26 " ,
" surface_2 " : " #142638 " ,
" surface_3 " : " #1a324a " ,
" field " : " #09 1420 " ,
" line " : " #1e 4a6e " ,
" text " : " #d7ec ff " ,
" muted " : " #8a a7c0 " ,
" bg " : " #06 1019 " ,
" sidebar " : " #040b13 " ,
" surface " : " #0d1d29 " ,
" surface_2 " : " #13293a " ,
" surface_3 " : " #1a364c " ,
" field " : " #08 1420 " ,
" line " : " #1c 4a6e " ,
" text " : " #d8ed ff " ,
" muted " : " #86 a7c2 " ,
" accent " : " #3ddcff " ,
" accent_2 " : " #5a8 cff " ,
" danger " : " #ff6a6a " ,
" accent_hi " : " #8fe cff " ,
" accent_2 " : " #6f9bff " ,
" danger " : " #ff7a7a " ,
} ,
" sunset-orange " : {
" bg " : " #12 0d07 " ,
" sidebar " : " #0b08 04 " ,
" surface " : " #2217 11 " ,
" surface_2 " : " #2f2215 " ,
" surface_3 " : " #3b2d1d " ,
" field " : " #17 100a " ,
" line " : " #6e 4a1e " ,
" text " : " #ffe9d7 " ,
" muted " : " #c0a98a " ,
" bg " : " #13 0d06 " ,
" sidebar " : " #0e09 04 " ,
" surface " : " #2419 11 " ,
" surface_2 " : " #312419 " ,
" surface_3 " : " #3f2f20 " ,
" field " : " #18 1009 " ,
" line " : " #6c 4a1d " ,
" text " : " #ffead6 " ,
" muted " : " #c3ab8b " ,
" accent " : " #ff9d3d " ,
" accent_2 " : " #ff6a5a " ,
" danger " : " #ff6a6a " ,
" accent_hi " : " #ffbd7c " ,
" accent_2 " : " #ff7a5c " ,
" danger " : " #ff7a7a " ,
} ,
" cyber-purple " : {
" bg " : " #0f0711 " ,
" sidebar " : " #09 050b " ,
" surface " : " #1d0e22 " ,
" surface_2 " : " #2a1133 " ,
" surface_3 " : " #371a44 " ,
" field " : " #140918 " ,
" line " : " #5a1f6e " ,
" text " : " #f0d7 ff " ,
" muted " : " #b38ac0 " ,
" bg " : " #0f0712 " ,
" sidebar " : " #0a 050c " ,
" surface " : " #1e0f24 " ,
" surface_2 " : " #2b1534 " ,
" surface_3 " : " #3a1d46 " ,
" field " : " #150a19 " ,
" line " : " #5c2172 " ,
" text " : " #f2d9 ff " ,
" muted " : " #b590c2 " ,
" accent " : " #c95aff " ,
" accent_hi " : " #e0a0ff " ,
" accent_2 " : " #6affcc " ,
" danger " : " #ff6a6 a " ,
" danger " : " #ff7a7 a " ,
} ,
}
@@ -358,10 +362,15 @@ class PulseGateDesktop(tk.Tk):
stats = self . _card ( sidebar , bg = COLORS [ " surface " ] , padx = 14 , pady = 12 )
stats . pack ( fill = " x " , padx = 22 , pady = ( 28 , 10 ) )
self . server_count_label = self . _label ( stats , " " , bg = COLORS [ " surface " ] , fg = COLORS [ " text " ] , font = FONT_CARD )
self . server_count_label . pack ( anchor = " w " )
head = tk . Frame ( stats , bg = COLORS [ " surface " ] )
head . pack ( fill = " x " )
self . _label ( head , " PulseGate " , bg = COLORS [ " surface " ] , fg = COLORS [ " text " ] , font = FONT_CARD ) . pack ( side = " left " )
self . _label ( head , " v " + APP_VERSION , bg = COLORS [ " surface " ] , fg = COLORS [ " muted " ] , font = FONT_SMALL ) . pack ( side = " right " , anchor = " e " , pady = ( 3 , 0 ) )
terminal_text = " Terminal bereit " if self . terminal else " Kein Terminal gefunden "
self . _label ( stats , terminal_text , bg = COLORS [ " surface " ] , fg = COLORS [ " accent_2 " ] , font = FONT_SMALL ) . pack ( anchor = " w " , pady = ( 7 , 0 ) )
terminal_fg = STATUS_OK if self . terminal else COLORS [ " danger " ]
self . _label ( stats , f " ● { terminal_text } " , bg = COLORS [ " surface " ] , fg = terminal_fg , font = FONT_SMALL ) . pack ( anchor = " w " , pady = ( 8 , 0 ) )
self . server_count_label = self . _label ( stats , " 0 Server " , bg = COLORS [ " surface " ] , fg = COLORS [ " accent " ] , font = ( UI_FONT [ 0 ] , 10 , " bold " ) )
self . server_count_label . pack ( anchor = " w " , pady = ( 7 , 0 ) )
self . _label ( stats , str ( self . path ) , bg = COLORS [ " surface " ] , fg = COLORS [ " muted " ] , font = FONT_SMALL , wraplength = 210 ) . pack ( anchor = " w " , pady = ( 8 , 0 ) )
self . _button ( sidebar , " Über PulseGate " , self . _about , variant = " ghost " ) . pack ( fill = " x " , padx = 22 , side = " bottom " , pady = ( 0 , 16 ) )
@@ -399,7 +408,17 @@ class PulseGateDesktop(tk.Tk):
left . pack_propagate ( False )
self . server_canvas = tk . Canvas ( left , bg = COLORS [ " bg " ] , highlightthickness = 0 , bd = 0 )
scrollbar = tk . Scrollbar ( left , orient = " vertical " , command = self . server_canvas . yview , bg = COLORS [ " bg " ] , troughcolor = COLORS [ " bg " ] )
scrollbar = tk . Scrollbar (
left ,
orient = " vertical " ,
command = self . server_canvas . yview ,
bg = COLORS [ " surface_3 " ] ,
activebackground = COLORS [ " accent " ] ,
troughcolor = COLORS [ " bg " ] ,
relief = " flat " ,
bd = 0 ,
width = 6 ,
)
self . server_cards = tk . Frame ( self . server_canvas , bg = COLORS [ " bg " ] )
self . server_cards . bind ( " <Configure> " , lambda _event : self . server_canvas . configure ( scrollregion = self . server_canvas . bbox ( " all " ) ) )
self . server_canvas . create_window ( ( 0 , 0 ) , window = self . server_cards , anchor = " nw " , width = 382 )
@@ -567,16 +586,27 @@ class PulseGateDesktop(tk.Tk):
text = group ,
bg = COLORS [ " accent " ] if selected else COLORS [ " surface_2 " ] ,
fg = " #001a10 " if selected else COLORS [ " text " ] ,
font = FONT_SMALL ,
padx = 9 ,
pady = 4 ,
font = ( UI_FONT [ 0 ] , 9 , " bold " ) ,
padx = 10 ,
pady = 5 ,
cursor = " hand2 " ,
highlightthickness = 1 ,
highlightbackground = COLORS [ " line " ] ,
highlightbackground = COLORS [ " line " ] if not selected else COLORS [ " accent_hi " ] ,
)
chip . pack ( side = " left " , padx = ( 0 , 6 ) )
chip . bind ( " <Button-1> " , lambda _event , value = group : self . _set_group_filter ( value ) )
def _enter ( _event , label = chip , is_selected = selected ) - > None :
if not is_selected :
label . configure ( bg = COLORS [ " surface_3 " ] , highlightbackground = COLORS [ " accent_hi " ] )
def _leave ( _event , label = chip , is_selected = selected ) - > None :
if not is_selected :
label . configure ( bg = COLORS [ " surface_2 " ] , highlightbackground = COLORS [ " line " ] )
chip . bind ( " <Enter> " , _enter )
chip . bind ( " <Leave> " , _leave )
def _set_group_filter ( self , group : str ) - > None :
self . group_filter = group
self . _refresh_group_chips ( )
@@ -611,43 +641,61 @@ class PulseGateDesktop(tk.Tk):
selected = index == self . selected_index
bg = COLORS [ " surface_2 " ] if selected else COLORS [ " surface " ]
border = COLORS [ " accent " ] if selected else COLORS [ " line " ]
card = tk . Frame ( parent , bg = border , bd = 0 )
inner = tk . Frame ( card , bg = bg , padx = 14 , pady = 12 )
inner . pack ( fill = " both " , expand = True , padx = 1 , pady = 1 )
target = f " { server . get ( ' user ' ) } @ { server . get ( ' host ' ) } : { server . get ( ' port ' ) or 22 } "
name = server . get ( " name " ) or " Unbenannt "
group = server . get ( " group " ) or " Keine Gruppe "
head = tk . Frame ( inner , bg = bg )
head . pack ( fill = " x " , pady = ( 0 , 6 ) )
self . _label ( inner , name , bg = bg , fg = COLORS [ " text " ] , font = FONT_CARD ) . pack ( anchor = " w " )
self . _label ( inner , target , bg = bg , fg = COLORS [ " muted " ] , font = FONT_SMALL ) . pack ( anchor = " w " , pady = ( 5 , 8 ) )
name = server . get ( " name " ) or " Unbenannt "
self . _label ( head , name , bg = bg , fg = COLORS [ " text " ] , font = FONT_CARD ) . pack ( side = " left " )
status = self . reachability . get ( index )
if status is None :
status_text , status_fg = " … " , COLORS [ " muted " ]
elif status :
status_text , status_fg = " ● erreichbar " , STATUS_OK
else :
status_text , status_fg = " ● offline " , COLORS [ " danger " ]
status_label = self . _label ( head , status_text , bg = bg , fg = status_fg , font = FONT_SMALL )
status_label . pack ( side = " right " , anchor = " e " , pady = ( 2 , 0 ) )
self . reach_labels [ index ] = status_label
target = f " { server . get ( ' user ' ) } @ { server . get ( ' host ' ) } : { server . get ( ' port ' ) or 22 } "
self . _label ( inner , target , bg = bg , fg = COLORS [ " muted " ] , font = UI_FONT_MONO ) . pack ( anchor = " w " , pady = ( 0 , 10 ) )
chips = tk . Frame ( inner , bg = bg )
chips . pack ( fill = " x " )
self . _chip ( chips , group , COLORS [ " accent_2 " ] , bg = bg ) . pack ( side = " left " )
self . _chip ( chips , server . get ( " group " ) or " Keine Gruppe " , COLORS [ " accent_2 " ] , bg = bg ) . pack ( side = " left " )
self . _chip ( chips , server . get ( " auth " ) or " key " , COLORS [ " accent " ] , bg = bg ) . pack ( side = " left " , padx = ( 6 , 0 ) )
if server . get ( " kitty_fix " ) :
self . _chip ( chips , " kitty " , COLORS [ " muted " ] , bg = bg ) . pack ( side = " left " , padx = ( 6 , 0 ) )
status = self . reachability . get ( index )
if status is None :
status_text , status_fg = " ● prüfe ... " , COLORS [ " muted " ]
elif status :
status_text , status_fg = " ● erreichbar " , STATUS_OK
else :
status_text , status_fg = " ● nicht erreichbar " , COLORS [ " danger " ]
status_label = self . _label ( inner , status_text , bg = bg , fg = status_fg , font = FONT_SMALL )
status_label . pack ( anchor = " w " , pady = ( 8 , 0 ) )
self . reach_labels [ index ] = status_label
connect_hint = tk . Label ( inner , text = " Verbinden › " , bg = bg , fg = COLORS [ " accent_hi " ] , font = FONT_SMALL )
connect_hint . place ( relx = 1.0 , rely = 0.0 , anchor = " ne " , x = - 14 , y = 6 )
def _card_enter ( _event ) - > None :
card . configure ( bg = COLORS [ " accent_hi " ] )
connect_hint . lift ( )
def _card_leave ( _event ) - > None :
card . configure ( bg = border )
connect_hint . lower ( )
for widget in ( card , inner ) :
widget . bind ( " <Button-1> " , lambda _event , value = index : self . _select_server ( value ) )
widget . bind ( " <Double-Button-1> " , lambda _event , value = index : self . _connect_at ( value ) )
widget . bind ( " <Button-3> " , lambda event , value = index : self . _server_menu ( event , value ) )
widget . bind ( " <Enter> " , _card_enter )
widget . bind ( " <Leave> " , _card_leave )
for child in inner . winfo_children ( ) :
child . bind ( " <Button-1> " , lambda _event , value = index : self . _select_server ( value ) )
child . bind ( " <Double-Button-1> " , lambda _event , value = index : self . _connect_at ( value ) )
child . bind ( " <Button-3> " , lambda event , value = index : self . _server_menu ( event , value ) )
child . bind ( " <Enter> " , _card_enter )
child . bind ( " <Leave> " , _card_leave )
return card
@@ -698,8 +746,13 @@ class PulseGateDesktop(tk.Tk):
for command in commands :
card = self . _card ( self . commands_host , bg = COLORS [ " surface " ] , padx = 16 , pady = 14 )
card . pack ( fill = " x " , pady = ( 0 , 10 ) )
self . _label ( card , command . get ( " name " ) or " Command " , bg = COLORS [ " surface " ] , fg = COLORS [ " text " ] , font = FONT_CARD ) . pack ( anchor = " w " )
self . _label ( card , command . get ( " command " ) or " " , bg = COLORS [ " surface " ] , fg = COLORS [ " accent " ] , font = UI_FONT_MONO ) . pack ( anchor = " w " , pady = ( 7 , 0 ) )
head = tk . Frame ( card , bg = COLORS [ " surface " ] )
head . pack ( fill = " x " )
self . _label ( head , " » " , bg = COLORS [ " surface " ] , fg = COLORS [ " accent " ] , font = ( UI_FONT [ 0 ] , 14 , " bold " ) ) . pack ( side = " left " , padx = ( 0 , 8 ) )
self . _label ( head , command . get ( " name " ) or " Command " , bg = COLORS [ " surface " ] , fg = COLORS [ " text " ] , font = FONT_CARD ) . pack ( side = " left " )
self . _label ( card , command . get ( " command " ) or " " , bg = COLORS [ " surface " ] , fg = COLORS [ " accent " ] , font = UI_FONT_MONO ) . pack ( anchor = " w " , pady = ( 9 , 0 ) )
actions = tk . Frame ( card , bg = COLORS [ " surface " ] )
actions . pack ( fill = " x " , pady = ( 12 , 0 ) )
@@ -1009,36 +1062,44 @@ class PulseGateDesktop(tk.Tk):
return widget
def _button ( self , parent : tk . Widget , text : str , command , * , variant : str = " primary " ) - > tk . Button :
bg = COLORS [ " accent " ]
fg = " #001a10 "
active_bg = COLORS [ " accent " ]
if variant == " ghost " :
if variant == " primary " :
bg = COLORS [ " accent " ]
fg = " #001a10 "
hover_bg = COLORS [ " accent_hi " ]
hover_fg = " #001a10 "
elif variant == " ghost " :
bg = COLORS [ " surface_2 " ]
fg = COLORS [ " text " ]
active_bg = COLORS [ " surface_3 " ]
elif variant == " danger " :
bg = " #351414 "
hover_bg = COLORS [ " surface_3 " ]
hover_fg = COLORS [ " text " ]
else :
bg = " #3a1717 "
fg = COLORS [ " danger " ]
active_bg = " #4a1d1d "
hover_bg = " #522020 "
hover_fg = " #ffd0d0 "
return tk . Button (
button = tk . Button (
parent ,
text = text ,
command = command ,
bg = bg ,
fg = fg ,
activebackground = active_bg ,
activeforeground = fg ,
activebackground = hover_bg ,
activeforeground = hover_fg ,
relief = " flat " ,
bd = 0 ,
padx = 14 ,
pady = 9 ,
font = ( UI_FONT [ 0 ] , 10 , " bold " ) ,
cursor = " hand2 " ,
highlightthickness = 0 ,
)
button . bind ( " <Enter> " , lambda _event : button . configure ( bg = hover_bg , fg = hover_fg ) )
button . bind ( " <Leave> " , lambda _event : button . configure ( bg = bg , fg = fg ) )
return button
def _nav_button ( self , parent : tk . Widget , text : str , command ) - > tk . Button :
return tk . Button (
button = tk . Button (
parent ,
text = text ,
command = command ,
@@ -1057,6 +1118,24 @@ class PulseGateDesktop(tk.Tk):
highlightbackground = COLORS [ " sidebar " ] ,
)
def _enter ( _event ) - > None :
if not self . _view_active ( text ) :
button . configure ( bg = COLORS [ " surface_2 " ] )
def _leave ( _event ) - > None :
if self . _view_active ( text ) :
button . configure ( bg = COLORS [ " surface_2 " ] , highlightbackground = COLORS [ " line " ] )
else :
button . configure ( bg = COLORS [ " sidebar " ] , highlightbackground = COLORS [ " sidebar " ] )
button . bind ( " <Enter> " , _enter )
button . bind ( " <Leave> " , _leave )
return button
def _view_active ( self , name : str ) - > bool :
mapping = { " Server " : " servers " , " Commands " : " commands " , " Settings " : " settings " }
return mapping . get ( name ) == self . current_view
def _card ( self , parent : tk . Widget , * , bg : str , padx : int , pady : int ) - > tk . Frame :
return tk . Frame ( parent , bg = bg , padx = padx , pady = pady , highlightthickness = 1 , highlightbackground = COLORS [ " line " ] )
@@ -1066,16 +1145,17 @@ class PulseGateDesktop(tk.Tk):
text = text ,
bg = bg or parent . cget ( " bg " ) ,
fg = fg ,
font = FONT_SMALL ,
font = ( UI_FONT [ 0 ] , 9 , " bold " ) ,
padx = 7 ,
pady = 3 ,
highlightthickness = 1 ,
highlightbackground = COLORS [ " line " ] ,
highlightbackground = COLORS [ " surface_3 " ] ,
)
def _empty_state ( self , parent : tk . Widget , text : str ) - > tk . Frame :
def _empty_state ( self , parent : tk . Widget , text : str , symbol : str = " ◦ " ) - > tk . Frame :
card = self . _card ( parent , bg = COLORS [ " surface " ] , padx = 16 , pady = 18 )
self . _label ( card , text , bg = COLORS [ " surface " ] , fg = COLORS [ " muted " ] , font = FONT ) . pack ( anchor = " w " )
self . _label ( card , symbol , bg = COLORS [ " surface " ] , fg = COLORS [ " accent " ] , font = ( UI_FONT [ 0 ] , 18 , " bold " ) ) . pack ( side = " left " , padx = ( 0 , 12 ) )
self . _label ( card , text , bg = COLORS [ " surface " ] , fg = COLORS [ " muted " ] , font = FONT ) . pack ( side = " left " )
return card