""" Server add/edit dialog — modal window with all server fields. """ import customtkinter as ctk from core.server_store import SERVER_TYPES, DEFAULT_PORTS class ServerDialog(ctk.CTkToplevel): def __init__(self, master, store, server: dict | None = None): super().__init__(master) self.store = store self.editing = server self.result = None self.title("Edit Server" if server else "Add Server") self.geometry("450x520") self.resizable(False, False) self.grab_set() # Center on parent self.transient(master) self._build_ui(server) def _build_ui(self, server: dict | None): pad = {"padx": 20, "pady": (5, 0)} entry_pad = {"padx": 20, "pady": (2, 5)} # Alias ctk.CTkLabel(self, text="Alias", anchor="w").pack(fill="x", **pad) self.alias_entry = ctk.CTkEntry(self, placeholder_text="my-server") self.alias_entry.pack(fill="x", **entry_pad) # IP ctk.CTkLabel(self, text="IP / Hostname", anchor="w").pack(fill="x", **pad) self.ip_entry = ctk.CTkEntry(self, placeholder_text="1.2.3.4") self.ip_entry.pack(fill="x", **entry_pad) # Type + Port row row = ctk.CTkFrame(self, fg_color="transparent") row.pack(fill="x", padx=20, pady=(5, 5)) type_frame = ctk.CTkFrame(row, fg_color="transparent") type_frame.pack(side="left", fill="x", expand=True, padx=(0, 5)) ctk.CTkLabel(type_frame, text="Type", anchor="w").pack(fill="x") self.type_var = ctk.StringVar(value="ssh") self.type_menu = ctk.CTkOptionMenu( type_frame, values=SERVER_TYPES, variable=self.type_var, command=self._on_type_change ) self.type_menu.pack(fill="x") port_frame = ctk.CTkFrame(row, fg_color="transparent") port_frame.pack(side="left", fill="x", expand=True, padx=(5, 0)) ctk.CTkLabel(port_frame, text="Port", anchor="w").pack(fill="x") self.port_entry = ctk.CTkEntry(port_frame, placeholder_text="22") self.port_entry.pack(fill="x") # User ctk.CTkLabel(self, text="Username", anchor="w").pack(fill="x", **pad) self.user_entry = ctk.CTkEntry(self, placeholder_text="root") self.user_entry.pack(fill="x", **entry_pad) # Password ctk.CTkLabel(self, text="Password", anchor="w").pack(fill="x", **pad) pass_frame = ctk.CTkFrame(self, fg_color="transparent") pass_frame.pack(fill="x", padx=20, pady=(2, 5)) self.password_entry = ctk.CTkEntry(pass_frame, show="*", placeholder_text="password") self.password_entry.pack(side="left", fill="x", expand=True, padx=(0, 5)) self.show_pass = ctk.CTkButton(pass_frame, text="Show", width=60, command=self._toggle_password) self.show_pass.pack(side="right") self._pass_visible = False # Notes ctk.CTkLabel(self, text="Notes", anchor="w").pack(fill="x", **pad) self.notes_entry = ctk.CTkEntry(self, placeholder_text="optional description") self.notes_entry.pack(fill="x", **entry_pad) # Buttons btn_frame = ctk.CTkFrame(self, fg_color="transparent") btn_frame.pack(fill="x", padx=20, pady=(15, 20)) ctk.CTkButton(btn_frame, text="Cancel", fg_color="#6b7280", command=self.destroy).pack(side="left", expand=True, padx=(0, 5)) ctk.CTkButton(btn_frame, text="Save", command=self._save).pack(side="right", expand=True, padx=(5, 0)) # Fill values if editing if server: self.alias_entry.insert(0, server.get("alias", "")) self.alias_entry.configure(state="disabled") self.ip_entry.insert(0, server.get("ip", "")) self.type_var.set(server.get("type", "ssh")) self.port_entry.insert(0, str(server.get("port", 22))) self.user_entry.insert(0, server.get("user", "")) self.password_entry.insert(0, server.get("password", "")) self.notes_entry.insert(0, server.get("notes", "")) def _on_type_change(self, value): default_port = DEFAULT_PORTS.get(value, 22) self.port_entry.delete(0, "end") self.port_entry.insert(0, str(default_port)) def _toggle_password(self): self._pass_visible = not self._pass_visible self.password_entry.configure(show="" if self._pass_visible else "*") self.show_pass.configure(text="Hide" if self._pass_visible else "Show") def _save(self): alias = self.alias_entry.get().strip() ip = self.ip_entry.get().strip() port_str = self.port_entry.get().strip() user = self.user_entry.get().strip() password = self.password_entry.get() server_type = self.type_var.get() notes = self.notes_entry.get().strip() # Validation if not alias: self._show_error("Alias is required") return if not ip: self._show_error("IP is required") return try: port = int(port_str) if port_str else DEFAULT_PORTS.get(server_type, 22) except ValueError: self._show_error("Port must be a number") return server_data = { "alias": alias, "ip": ip, "port": port, "user": user or "root", "password": password, "type": server_type, "notes": notes, } try: if self.editing: self.store.update_server(alias, server_data) else: self.store.add_server(server_data) self.result = server_data self.destroy() except ValueError as e: self._show_error(str(e)) def _show_error(self, message: str): # Simple error via title flash self.title(f"Error: {message}") self.after(2000, lambda: self.title("Edit Server" if self.editing else "Add Server"))