v1.9.27: add disconnect button in terminal + disconnect in context menu
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,7 @@ _EN = {
|
||||
"term_connecting": "Connecting to {alias}...",
|
||||
"term_connected": "Connected to {alias}",
|
||||
"term_disconnected": "Disconnected",
|
||||
"ctx_disconnect": "Disconnect",
|
||||
"term_click_to_connect": "Double-click to connect to {alias}",
|
||||
"sftp_click_to_connect": "Double-click server to browse files",
|
||||
"term_reconnecting": "Reconnecting ({n}/{max})...",
|
||||
@@ -636,6 +637,7 @@ _RU = {
|
||||
"term_connecting": "Подключение к {alias}...",
|
||||
"term_connected": "Подключено к {alias}",
|
||||
"term_disconnected": "Отключено",
|
||||
"ctx_disconnect": "Отключиться",
|
||||
"term_click_to_connect": "Двойной клик для подключения к {alias}",
|
||||
"sftp_click_to_connect": "Двойной клик для просмотра файлов",
|
||||
"term_reconnecting": "Переподключение ({n}/{max})...",
|
||||
@@ -1161,6 +1163,7 @@ _ZH = {
|
||||
"term_connecting": "正在连接 {alias}...",
|
||||
"term_connected": "已连接到 {alias}",
|
||||
"term_disconnected": "已断开",
|
||||
"ctx_disconnect": "断开连接",
|
||||
"term_click_to_connect": "双击连接 {alias}",
|
||||
"sftp_click_to_connect": "双击服务器浏览文件",
|
||||
"term_reconnecting": "重新连接中 ({n}/{max})...",
|
||||
|
||||
@@ -210,6 +210,7 @@ CTX_ICONS = {
|
||||
"ctx_open_browser": "browser",
|
||||
"ctx_check_status": "status_check",
|
||||
"ctx_copy_alias": "copy",
|
||||
"ctx_disconnect": "close",
|
||||
"edit": "edit",
|
||||
"delete": "delete",
|
||||
}
|
||||
|
||||
@@ -256,3 +256,13 @@ class SessionPool:
|
||||
if has_active:
|
||||
active.append(alias)
|
||||
return active
|
||||
|
||||
def has_active_session(self, alias: str) -> bool:
|
||||
with self._lock:
|
||||
sd = self._sessions.get(alias)
|
||||
if not sd:
|
||||
return False
|
||||
return bool(
|
||||
(sd.shell_session and sd.shell_session.connected) or
|
||||
(sd.sftp_session and sd.sftp_session.connected)
|
||||
)
|
||||
14
gui/app.py
14
gui/app.py
@@ -163,6 +163,7 @@ class App(ctk.CTk):
|
||||
self.sidebar.open_tab_callback = self._context_open_tab
|
||||
self.sidebar.check_status_callback = self._context_check_status
|
||||
self.sidebar.open_browser_callback = self._context_open_browser
|
||||
self.sidebar.disconnect_callback = self._on_server_disconnect
|
||||
|
||||
# Main area
|
||||
self._main_frame = ctk.CTkFrame(self._paned, fg_color="transparent")
|
||||
@@ -264,6 +265,11 @@ class App(ctk.CTk):
|
||||
widget.pack(fill="both", expand=True)
|
||||
self._tab_instances[key] = widget
|
||||
|
||||
# Wire disconnect callback for terminal toolbar button
|
||||
terminal = self._tab_instances.get("terminal")
|
||||
if terminal and hasattr(terminal, "_on_disconnect_callback"):
|
||||
terminal._on_disconnect_callback = self._on_server_disconnect
|
||||
|
||||
# Restore previously active tab if still available
|
||||
if restore_tab_key and restore_tab_key in self._tab_keys:
|
||||
try:
|
||||
@@ -313,6 +319,14 @@ class App(ctk.CTk):
|
||||
if hasattr(widget, "connect"):
|
||||
widget.connect()
|
||||
|
||||
def _on_server_disconnect(self, alias: str):
|
||||
"""Disconnect all sessions for a server."""
|
||||
for key, widget in self._tab_instances.items():
|
||||
if hasattr(widget, "disconnect"):
|
||||
widget.disconnect()
|
||||
self.session_pool.disconnect_session(alias)
|
||||
self.after(500, self.sidebar.update_session_indicators)
|
||||
|
||||
def _add_server(self):
|
||||
dialog = ServerDialog(self, self.store)
|
||||
self.wait_window(dialog)
|
||||
|
||||
@@ -97,6 +97,7 @@ class Sidebar(ctk.CTkFrame):
|
||||
self.open_tab_callback = None # (alias, tab_key) → select server + switch tab
|
||||
self.check_status_callback = None # (alias) → check single server
|
||||
self.open_browser_callback = None # (alias) → open server URL in browser
|
||||
self.disconnect_callback = None # (alias) → disconnect all sessions
|
||||
|
||||
# Subscribe to store changes
|
||||
self.store.subscribe(self._refresh_list)
|
||||
@@ -467,6 +468,18 @@ class Sidebar(ctk.CTkFrame):
|
||||
if actions:
|
||||
menu.add_separator()
|
||||
|
||||
# Dynamic disconnect if session is active
|
||||
if self.session_pool and self.session_pool.has_active_session(alias):
|
||||
dc_icon = icon(CTX_ICONS.get("ctx_disconnect", ""))
|
||||
dc_label = f"{dc_icon} {t('ctx_disconnect')}" if dc_icon else t("ctx_disconnect")
|
||||
menu.add_command(
|
||||
label=dc_label,
|
||||
command=lambda a=alias: (
|
||||
self.disconnect_callback(a) if self.disconnect_callback else None
|
||||
),
|
||||
)
|
||||
menu.add_separator()
|
||||
|
||||
# "Move to Group" submenu
|
||||
groups = self.store.get_groups()
|
||||
if groups:
|
||||
|
||||
@@ -318,6 +318,10 @@ class FilesTab(ctk.CTkFrame):
|
||||
if self._current_alias and not self._sftp:
|
||||
self._connect_sftp()
|
||||
|
||||
def disconnect(self):
|
||||
"""Disconnect SFTP and update UI (called by app)."""
|
||||
self._disconnect_sftp()
|
||||
|
||||
# ── SFTP connection ──
|
||||
|
||||
def _connect_sftp(self):
|
||||
|
||||
@@ -104,6 +104,12 @@ class PowershellTab(ctk.CTkFrame):
|
||||
if self._current_alias and not self._client:
|
||||
self._connect(self._current_alias)
|
||||
|
||||
def disconnect(self):
|
||||
"""Disconnect WinRM and update UI (called by app)."""
|
||||
self._disconnect()
|
||||
if self._current_alias:
|
||||
self._set_status(t("term_click_to_connect").format(alias=self._current_alias), "#f59e0b")
|
||||
|
||||
# ── Connection ───────────────────────────────────────────────────
|
||||
|
||||
def _connect(self, alias: str):
|
||||
|
||||
@@ -29,6 +29,17 @@ class TerminalTab(ctk.CTkFrame):
|
||||
# Import here to avoid circular issues
|
||||
from gui.widgets.terminal_widget import TerminalWidget
|
||||
|
||||
self._toolbar = ctk.CTkFrame(self, fg_color="transparent", height=32)
|
||||
self._toolbar.pack(fill="x", padx=5, pady=(5, 0))
|
||||
self._toolbar.pack_propagate(False)
|
||||
self._disconnect_btn = ctk.CTkButton(
|
||||
self._toolbar, text=t("ctx_disconnect"), width=110, height=28,
|
||||
fg_color="#dc2626", hover_color="#b91c1c",
|
||||
font=ctk.CTkFont(size=12), state="disabled",
|
||||
command=self._on_disconnect_click,
|
||||
)
|
||||
self._disconnect_btn.pack(side="right", padx=2)
|
||||
|
||||
self._terminal = TerminalWidget(
|
||||
self,
|
||||
send_callback=self._send_to_shell,
|
||||
@@ -45,6 +56,7 @@ class TerminalTab(ctk.CTkFrame):
|
||||
# Sudo auto-password detection
|
||||
self._sudo_buffer = b"" # Buffer for detecting sudo prompts
|
||||
self._sudo_sent = False # Prevent sending password twice for same prompt
|
||||
self._on_disconnect_callback = None
|
||||
|
||||
def set_server(self, alias: str | None):
|
||||
if alias == self._current_alias:
|
||||
@@ -61,6 +73,7 @@ class TerminalTab(ctk.CTkFrame):
|
||||
|
||||
self._current_alias = alias
|
||||
if alias:
|
||||
self._disconnect_btn.configure(state="disabled")
|
||||
self._terminal.set_status(t("term_click_to_connect").format(alias=alias), "#f59e0b")
|
||||
else:
|
||||
self._terminal.reset()
|
||||
@@ -71,6 +84,17 @@ class TerminalTab(ctk.CTkFrame):
|
||||
if self._current_alias and not self._session:
|
||||
self._connect()
|
||||
|
||||
def _on_disconnect_click(self):
|
||||
if self._on_disconnect_callback and self._current_alias:
|
||||
self._on_disconnect_callback(self._current_alias)
|
||||
|
||||
def disconnect(self):
|
||||
"""Disconnect and update UI (called by app)."""
|
||||
self._disconnect()
|
||||
self._disconnect_btn.configure(state="disabled")
|
||||
if self._current_alias:
|
||||
self._terminal.set_status(t("term_click_to_connect").format(alias=self._current_alias), "#f59e0b")
|
||||
|
||||
def _connect(self):
|
||||
if not self._current_alias:
|
||||
return
|
||||
@@ -140,6 +164,7 @@ class TerminalTab(ctk.CTkFrame):
|
||||
# Only grab focus if terminal tab is currently visible
|
||||
if self._terminal.winfo_ismapped():
|
||||
self._terminal.focus_terminal()
|
||||
self._disconnect_btn.configure(state="normal")
|
||||
self.after(0, _set_session)
|
||||
except Exception as e:
|
||||
self.after(0, lambda: self._terminal.set_status(
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
"""Version info for ServerManager."""
|
||||
|
||||
__version__ = "1.9.26"
|
||||
__version__ = "1.9.27"
|
||||
__app_name__ = "ServerManager"
|
||||
__author__ = "aibot777"
|
||||
__description__ = "Desktop GUI for managing remote servers"
|
||||
|
||||
Reference in New Issue
Block a user