Modernize GUI appearance with contemporary styling, icons, and improved layout
This commit is contained in:
342
rdp_client.py
342
rdp_client.py
@@ -41,9 +41,12 @@ import sys
|
||||
class RDPClient:
|
||||
def __init__(self):
|
||||
self.root = tk.Tk()
|
||||
self.root.title("RDP Client - Professional (Press F1 for shortcuts)")
|
||||
self.root.geometry("1000x700")
|
||||
self.root.minsize(800, 600)
|
||||
self.root.title("RDP Client - Professional")
|
||||
self.root.geometry("1200x800")
|
||||
self.root.minsize(900, 650)
|
||||
|
||||
# Modern styling
|
||||
self._setup_modern_theme()
|
||||
|
||||
# Configuration
|
||||
self.config_dir = os.path.expanduser("~/.config/rdp-client")
|
||||
@@ -83,6 +86,96 @@ class RDPClient:
|
||||
|
||||
self.logger.info("RDP Client initialized successfully")
|
||||
|
||||
def _setup_modern_theme(self):
|
||||
"""Setup modern visual theme and styling"""
|
||||
# Configure the main window
|
||||
self.root.configure(bg='#f8f9fa')
|
||||
|
||||
# Create and configure a modern style
|
||||
style = ttk.Style()
|
||||
|
||||
# Try to use a modern theme if available
|
||||
try:
|
||||
style.theme_use('clam') # More modern than default
|
||||
except:
|
||||
pass
|
||||
|
||||
# Define modern color palette
|
||||
self.colors = {
|
||||
'primary': '#0066cc', # Modern blue
|
||||
'primary_dark': '#004499', # Darker blue for hover
|
||||
'secondary': '#6c757d', # Muted gray
|
||||
'success': '#28a745', # Green
|
||||
'danger': '#dc3545', # Red
|
||||
'warning': '#ffc107', # Yellow
|
||||
'info': '#17a2b8', # Cyan
|
||||
'light': '#f8f9fa', # Light gray
|
||||
'dark': '#343a40', # Dark gray
|
||||
'white': '#ffffff',
|
||||
'border': '#dee2e6', # Light border
|
||||
'hover': '#e9ecef' # Hover gray
|
||||
}
|
||||
|
||||
# Configure ttk styles with modern colors
|
||||
style.configure('Modern.TFrame',
|
||||
background=self.colors['white'],
|
||||
relief='flat',
|
||||
borderwidth=1)
|
||||
|
||||
style.configure('Card.TFrame',
|
||||
background=self.colors['white'],
|
||||
relief='solid',
|
||||
borderwidth=1,
|
||||
bordercolor=self.colors['border'])
|
||||
|
||||
style.configure('Modern.TLabel',
|
||||
background=self.colors['white'],
|
||||
foreground=self.colors['dark'],
|
||||
font=('Segoe UI', 10))
|
||||
|
||||
style.configure('Title.TLabel',
|
||||
background=self.colors['white'],
|
||||
foreground=self.colors['dark'],
|
||||
font=('Segoe UI', 12, 'bold'))
|
||||
|
||||
style.configure('Modern.TButton',
|
||||
background=self.colors['primary'],
|
||||
foreground=self.colors['white'],
|
||||
borderwidth=0,
|
||||
focuscolor='none',
|
||||
font=('Segoe UI', 9))
|
||||
|
||||
style.map('Modern.TButton',
|
||||
background=[('active', self.colors['primary_dark']),
|
||||
('pressed', self.colors['primary_dark'])])
|
||||
|
||||
style.configure('Success.TButton',
|
||||
background=self.colors['success'],
|
||||
foreground=self.colors['white'],
|
||||
borderwidth=0,
|
||||
focuscolor='none',
|
||||
font=('Segoe UI', 9))
|
||||
|
||||
style.configure('Danger.TButton',
|
||||
background=self.colors['danger'],
|
||||
foreground=self.colors['white'],
|
||||
borderwidth=0,
|
||||
focuscolor='none',
|
||||
font=('Segoe UI', 9))
|
||||
|
||||
style.configure('Modern.Treeview',
|
||||
background=self.colors['white'],
|
||||
foreground=self.colors['dark'],
|
||||
fieldbackground=self.colors['white'],
|
||||
borderwidth=1,
|
||||
relief='solid')
|
||||
|
||||
style.configure('Modern.Treeview.Heading',
|
||||
background=self.colors['light'],
|
||||
foreground=self.colors['dark'],
|
||||
relief='flat',
|
||||
font=('Segoe UI', 9, 'bold'))
|
||||
|
||||
def _setup_logging(self):
|
||||
"""Setup logging configuration"""
|
||||
self.logger = logging.getLogger('rdp_client')
|
||||
@@ -342,13 +435,9 @@ class RDPClient:
|
||||
return fallback
|
||||
|
||||
def _setup_gui(self):
|
||||
"""Setup the main GUI"""
|
||||
# Configure style
|
||||
style = ttk.Style()
|
||||
style.theme_use('clam')
|
||||
|
||||
# Main container
|
||||
main_frame = ttk.Frame(self.root, padding="10")
|
||||
"""Setup the main GUI with modern styling"""
|
||||
# Main container with modern styling
|
||||
main_frame = ttk.Frame(self.root, style='Modern.TFrame', padding="20")
|
||||
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
|
||||
# Configure grid weights
|
||||
@@ -357,92 +446,149 @@ class RDPClient:
|
||||
main_frame.columnconfigure(1, weight=1)
|
||||
main_frame.rowconfigure(1, weight=1)
|
||||
|
||||
# Title
|
||||
title_label = ttk.Label(main_frame, text="RDP Client - Professional",
|
||||
font=('Arial', 16, 'bold'))
|
||||
title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
|
||||
# Modern header with icon and title
|
||||
header_frame = ttk.Frame(main_frame, style='Modern.TFrame')
|
||||
header_frame.grid(row=0, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 20))
|
||||
header_frame.columnconfigure(1, weight=1)
|
||||
|
||||
# Left panel - Connections
|
||||
left_frame = ttk.Frame(main_frame)
|
||||
left_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10))
|
||||
left_frame.rowconfigure(1, weight=1)
|
||||
left_frame.rowconfigure(1, weight=1) # Only saved connections now
|
||||
left_frame.columnconfigure(0, weight=1)
|
||||
# Title with modern styling
|
||||
title_label = ttk.Label(header_frame, text="🖥️ RDP Client Professional",
|
||||
style='Title.TLabel', font=('Segoe UI', 18, 'bold'))
|
||||
title_label.grid(row=0, column=0, sticky=tk.W)
|
||||
|
||||
# Saved Connections (now the main/only connections list)
|
||||
saved_label = ttk.Label(left_frame, text="Saved Connections", font=('Arial', 10, 'bold'))
|
||||
saved_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 5))
|
||||
# Help button in header
|
||||
help_btn = ttk.Button(header_frame, text="❓ Help (F1)",
|
||||
command=self._show_help, style='Modern.TButton')
|
||||
help_btn.grid(row=0, column=2, sticky=tk.E)
|
||||
|
||||
# Saved connections frame with listbox and scrollbar
|
||||
saved_frame = ttk.Frame(left_frame)
|
||||
saved_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
saved_frame.rowconfigure(0, weight=1)
|
||||
saved_frame.columnconfigure(0, weight=1)
|
||||
# Left panel - Connections (modern card style)
|
||||
left_card = ttk.Frame(main_frame, style='Card.TFrame', padding="15")
|
||||
left_card.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 15))
|
||||
left_card.rowconfigure(1, weight=1)
|
||||
left_card.columnconfigure(0, weight=1)
|
||||
|
||||
self.connections_listbox = tk.Listbox(saved_frame, font=('Arial', 9), width=30)
|
||||
scrollbar = ttk.Scrollbar(saved_frame, orient="vertical", command=self.connections_listbox.yview)
|
||||
# Connections header with count
|
||||
connections_header = ttk.Frame(left_card, style='Modern.TFrame')
|
||||
connections_header.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 15))
|
||||
connections_header.columnconfigure(0, weight=1)
|
||||
|
||||
self.connections_title = ttk.Label(connections_header, text="📁 Saved Connections",
|
||||
style='Title.TLabel')
|
||||
self.connections_title.grid(row=0, column=0, sticky=tk.W)
|
||||
|
||||
# Modern connections listbox with better styling
|
||||
listbox_frame = ttk.Frame(left_card, style='Modern.TFrame')
|
||||
listbox_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
listbox_frame.rowconfigure(0, weight=1)
|
||||
listbox_frame.columnconfigure(0, weight=1)
|
||||
|
||||
# Replace old listbox with modern styling
|
||||
self.connections_listbox = tk.Listbox(
|
||||
listbox_frame,
|
||||
font=('Segoe UI', 10),
|
||||
bg=self.colors['white'],
|
||||
fg=self.colors['dark'],
|
||||
selectbackground=self.colors['primary'],
|
||||
selectforeground=self.colors['white'],
|
||||
borderwidth=0,
|
||||
highlightthickness=1,
|
||||
highlightcolor=self.colors['primary'],
|
||||
relief='flat',
|
||||
activestyle='none'
|
||||
)
|
||||
|
||||
scrollbar = ttk.Scrollbar(listbox_frame, orient="vertical", command=self.connections_listbox.yview)
|
||||
self.connections_listbox.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
self.connections_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
self.connections_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 2))
|
||||
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
|
||||
|
||||
# Connection buttons
|
||||
conn_buttons_frame = ttk.Frame(left_frame)
|
||||
conn_buttons_frame.grid(row=2, column=0, pady=(10, 0), sticky=(tk.W, tk.E))
|
||||
# Modern action buttons with icons
|
||||
buttons_frame = ttk.Frame(left_card, style='Modern.TFrame')
|
||||
buttons_frame.grid(row=2, column=0, pady=(15, 0), sticky=(tk.W, tk.E))
|
||||
buttons_frame.columnconfigure(0, weight=1)
|
||||
buttons_frame.columnconfigure(1, weight=1)
|
||||
buttons_frame.columnconfigure(2, weight=1)
|
||||
|
||||
ttk.Button(conn_buttons_frame, text="Connect",
|
||||
command=self._connect_selected).pack(side=tk.LEFT, padx=(0, 5))
|
||||
ttk.Button(conn_buttons_frame, text="Edit",
|
||||
command=self._edit_selected).pack(side=tk.LEFT, padx=(0, 5))
|
||||
ttk.Button(conn_buttons_frame, text="Delete",
|
||||
command=self._delete_selected).pack(side=tk.LEFT)
|
||||
# Primary action buttons
|
||||
ttk.Button(buttons_frame, text="🔗 Connect",
|
||||
command=self._connect_selected, style='Success.TButton').grid(
|
||||
row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
|
||||
|
||||
# Right panel - Actions and Details
|
||||
right_frame = ttk.Frame(main_frame)
|
||||
right_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
right_frame.rowconfigure(1, weight=1)
|
||||
right_frame.columnconfigure(0, weight=1)
|
||||
ttk.Button(buttons_frame, text="✏️ Edit",
|
||||
command=self._edit_selected, style='Modern.TButton').grid(
|
||||
row=0, column=1, sticky=(tk.W, tk.E), padx=2.5)
|
||||
|
||||
# Actions frame
|
||||
actions_frame = ttk.LabelFrame(right_frame, text="Actions", padding="10")
|
||||
actions_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
|
||||
ttk.Button(buttons_frame, text="🗑️ Delete",
|
||||
command=self._delete_selected, style='Danger.TButton').grid(
|
||||
row=0, column=2, sticky=(tk.W, tk.E), padx=(5, 0))
|
||||
|
||||
ttk.Button(actions_frame, text="New Connection",
|
||||
command=self._new_connection, width=20).pack(pady=2)
|
||||
ttk.Button(actions_frame, text="Test Connection",
|
||||
command=self._test_selected_connection, width=20).pack(pady=2)
|
||||
ttk.Button(actions_frame, text="Import Connections",
|
||||
command=self._import_connections, width=20).pack(pady=2)
|
||||
ttk.Button(actions_frame, text="Export Connections",
|
||||
command=self._export_connections, width=20).pack(pady=2)
|
||||
ttk.Button(actions_frame, text="Clear All Credentials",
|
||||
command=self._clear_credentials, width=20).pack(pady=2)
|
||||
# Right panel - Actions and Details (modern card style)
|
||||
right_card = ttk.Frame(main_frame, style='Card.TFrame', padding="15")
|
||||
right_card.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
right_card.rowconfigure(1, weight=1)
|
||||
right_card.columnconfigure(0, weight=1)
|
||||
|
||||
# Details frame
|
||||
details_frame = ttk.LabelFrame(right_frame, text="Connection Details", padding="10")
|
||||
details_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
details_frame.columnconfigure(1, weight=1)
|
||||
# Actions section with modern styling
|
||||
actions_header = ttk.Label(right_card, text="⚡ Quick Actions", style='Title.TLabel')
|
||||
actions_header.grid(row=0, column=0, sticky=tk.W, pady=(0, 15))
|
||||
|
||||
# Details labels
|
||||
actions_frame = ttk.Frame(right_card, style='Modern.TFrame')
|
||||
actions_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=(0, 15))
|
||||
actions_frame.columnconfigure(0, weight=1)
|
||||
actions_frame.columnconfigure(1, weight=1)
|
||||
|
||||
ttk.Button(actions_frame, text="➕ New Connection",
|
||||
command=self._new_connection, style='Modern.TButton').grid(
|
||||
row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5), pady=3)
|
||||
|
||||
ttk.Button(actions_frame, text="🧪 Test Connection",
|
||||
command=self._test_selected_connection, style='Modern.TButton').grid(
|
||||
row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=3)
|
||||
|
||||
ttk.Button(actions_frame, text="📥 Import",
|
||||
command=self._import_connections, style='Modern.TButton').grid(
|
||||
row=1, column=0, sticky=(tk.W, tk.E), padx=(0, 5), pady=3)
|
||||
|
||||
ttk.Button(actions_frame, text="📤 Export",
|
||||
command=self._export_connections, style='Modern.TButton').grid(
|
||||
row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=3)
|
||||
|
||||
ttk.Button(actions_frame, text="🔐 Clear Credentials",
|
||||
command=self._clear_credentials, style='Danger.TButton').grid(
|
||||
row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=3)
|
||||
|
||||
# Details section with modern card styling
|
||||
details_label = ttk.Label(right_card, text="📋 Connection Details", style='Title.TLabel')
|
||||
details_label.grid(row=1, column=0, sticky=tk.W, pady=(20, 15))
|
||||
|
||||
details_card = ttk.Frame(right_card, style='Card.TFrame', padding="10")
|
||||
details_card.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
details_card.columnconfigure(1, weight=1)
|
||||
|
||||
# Modern details labels with better styling
|
||||
self.details_labels = {}
|
||||
details_fields = [
|
||||
("Server:", "server"),
|
||||
("Username:", "username"),
|
||||
("Domain:", "domain"),
|
||||
("Resolution:", "resolution"),
|
||||
("Color Depth:", "color_depth"),
|
||||
("Multi-Monitor:", "multimon"),
|
||||
("Sound:", "sound"),
|
||||
("Clipboard:", "clipboard"),
|
||||
("Drive Sharing:", "drives"),
|
||||
("Created:", "created")
|
||||
("🖥️ Server:", "server"),
|
||||
("👤 Username:", "username"),
|
||||
("🏢 Domain:", "domain"),
|
||||
("📺 Resolution:", "resolution"),
|
||||
("🎨 Color Depth:", "color_depth"),
|
||||
("🖼️ Multi-Monitor:", "multimon"),
|
||||
("🔊 Sound:", "sound"),
|
||||
("📋 Clipboard:", "clipboard"),
|
||||
("💾 Drive Sharing:", "drives"),
|
||||
("📅 Created:", "created")
|
||||
]
|
||||
|
||||
for i, (label, field) in enumerate(details_fields):
|
||||
ttk.Label(details_frame, text=label, font=('Arial', 9, 'bold')).grid(
|
||||
row=i, column=0, sticky=tk.W, pady=2)
|
||||
value_label = ttk.Label(details_frame, text="", font=('Arial', 9))
|
||||
value_label.grid(row=i, column=1, sticky=tk.W, padx=(10, 0), pady=2)
|
||||
label_widget = ttk.Label(details_card, text=label, style='Modern.TLabel',
|
||||
font=('Segoe UI', 9, 'bold'))
|
||||
label_widget.grid(row=i, column=0, sticky=tk.W, pady=4, padx=(0, 10))
|
||||
|
||||
value_label = ttk.Label(details_card, text="—", style='Modern.TLabel',
|
||||
font=('Segoe UI', 9))
|
||||
value_label.grid(row=i, column=1, sticky=tk.W, pady=4)
|
||||
self.details_labels[field] = value_label
|
||||
|
||||
# Bind listbox selection
|
||||
@@ -452,19 +598,19 @@ class RDPClient:
|
||||
# Keyboard shortcuts
|
||||
self._setup_keyboard_shortcuts()
|
||||
|
||||
# Bottom frame - Status
|
||||
status_frame = ttk.Frame(main_frame)
|
||||
status_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(10, 0))
|
||||
# Modern status bar
|
||||
status_frame = ttk.Frame(main_frame, style='Modern.TFrame')
|
||||
status_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(20, 0))
|
||||
status_frame.columnconfigure(0, weight=1)
|
||||
|
||||
self.status_var = tk.StringVar(value="Ready")
|
||||
self.status_var = tk.StringVar(value="✅ Ready")
|
||||
status_label = ttk.Label(status_frame, textvariable=self.status_var,
|
||||
font=('Arial', 9), foreground='gray')
|
||||
style='Modern.TLabel', font=('Segoe UI', 9))
|
||||
status_label.grid(row=0, column=0, sticky=tk.W)
|
||||
|
||||
# Exit button
|
||||
ttk.Button(status_frame, text="Exit",
|
||||
command=self.root.quit).grid(row=0, column=1, sticky=tk.E)
|
||||
# Modern exit button
|
||||
ttk.Button(status_frame, text="❌ Exit",
|
||||
command=self.root.quit, style='Danger.TButton').grid(row=0, column=1, sticky=tk.E)
|
||||
|
||||
def _setup_keyboard_shortcuts(self):
|
||||
"""Setup keyboard shortcuts for the application"""
|
||||
@@ -497,13 +643,11 @@ class RDPClient:
|
||||
|
||||
def _add_keyboard_shortcuts_info(self):
|
||||
"""Add keyboard shortcuts information to the status bar or help"""
|
||||
# You could create a help dialog or status tooltip here
|
||||
# For now, we'll add it to the window title when focused
|
||||
def show_shortcuts_hint(event):
|
||||
self.status_var.set("Shortcuts: Ctrl+N=New, Enter=Connect, Del=Delete, F2=Edit, Ctrl+T=Test, F5=Refresh, Ctrl+Q=Quit")
|
||||
self.status_var.set("⌨️ Shortcuts: Ctrl+N=New, Enter=Connect, Del=Delete, F2=Edit, Ctrl+T=Test, F5=Refresh, Ctrl+Q=Quit")
|
||||
|
||||
def clear_shortcuts_hint(event):
|
||||
self.status_var.set("Ready")
|
||||
self.status_var.set("✅ Ready")
|
||||
|
||||
# Show shortcuts hint when connections listbox gets focus
|
||||
self.connections_listbox.bind('<FocusIn>', show_shortcuts_hint)
|
||||
@@ -555,10 +699,14 @@ Multi-Monitor Support:
|
||||
for name in sorted(self.connections.keys(), key=str.lower):
|
||||
self.connections_listbox.insert(tk.END, name)
|
||||
|
||||
# Update connections count in title
|
||||
count = len(self.connections)
|
||||
self.connections_title.config(text=f"📁 Saved Connections ({count})")
|
||||
|
||||
# Clear details if no connections
|
||||
if not self.connections:
|
||||
for label in self.details_labels.values():
|
||||
label.config(text="")
|
||||
label.config(text="—")
|
||||
|
||||
def _update_details(self, conn):
|
||||
"""Update the details panel with connection info"""
|
||||
@@ -612,7 +760,7 @@ Multi-Monitor Support:
|
||||
self._add_to_history(name)
|
||||
|
||||
self._refresh_connections_list()
|
||||
self.status_var.set(f"Connection '{name}' saved successfully")
|
||||
self.status_var.set(f"💾 Connection '{name}' saved successfully")
|
||||
|
||||
def _edit_selected(self):
|
||||
"""Edit selected connection"""
|
||||
@@ -676,7 +824,7 @@ Multi-Monitor Support:
|
||||
self._add_to_history(new_name)
|
||||
|
||||
self._refresh_connections_list()
|
||||
self.status_var.set(f"Connection '{new_name}' updated successfully")
|
||||
self.status_var.set(f"✏️ Connection '{new_name}' updated successfully")
|
||||
|
||||
def _delete_selected(self):
|
||||
"""Delete selected connection"""
|
||||
@@ -707,7 +855,7 @@ Multi-Monitor Support:
|
||||
for label in self.details_labels.values():
|
||||
label.config(text="")
|
||||
|
||||
self.status_var.set(f"Connection '{name}' deleted")
|
||||
self.status_var.set(f"🗑️ Connection '{name}' deleted")
|
||||
|
||||
def _connect_selected(self, event=None):
|
||||
"""Connect to selected connection"""
|
||||
@@ -751,7 +899,7 @@ Multi-Monitor Support:
|
||||
username = conn['username']
|
||||
|
||||
self.logger.info(f"Attempting RDP connection to {server} as {username}")
|
||||
self.status_var.set(f"Connecting to {server}...")
|
||||
self.status_var.set(f"🔗 Connecting to {server}...")
|
||||
|
||||
# Test connectivity first
|
||||
if not self._test_server_connectivity(server):
|
||||
@@ -1050,7 +1198,7 @@ Multi-Monitor Support:
|
||||
self._save_credentials()
|
||||
self._refresh_connections_list()
|
||||
|
||||
self.status_var.set(f"Successfully imported {imported_count} connections")
|
||||
self.status_var.set(f"📥 Successfully imported {imported_count} connections")
|
||||
messagebox.showinfo("Import Complete", f"Successfully imported {imported_count} connections.")
|
||||
|
||||
except FileNotFoundError:
|
||||
@@ -1097,7 +1245,7 @@ Multi-Monitor Support:
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(export_data, f, indent=2)
|
||||
|
||||
self.status_var.set(f"Connections exported to {os.path.basename(file_path)}")
|
||||
self.status_var.set(f"📤 Connections exported to {os.path.basename(file_path)}")
|
||||
messagebox.showinfo("Export Complete", f"Successfully exported {len(self.connections)} connections to:\n{file_path}")
|
||||
|
||||
except Exception as e:
|
||||
@@ -1108,7 +1256,7 @@ Multi-Monitor Support:
|
||||
if messagebox.askyesno("Confirm", "Clear all saved passwords?"):
|
||||
self.credentials.clear()
|
||||
self._save_credentials()
|
||||
self.status_var.set("All credentials cleared")
|
||||
self.status_var.set("🔐 All credentials cleared")
|
||||
self.logger.info("All credentials cleared by user")
|
||||
|
||||
def _test_selected_connection(self):
|
||||
|
||||
Reference in New Issue
Block a user