diff --git a/core/i18n.py b/core/i18n.py index ba71677..d03fc93 100644 --- a/core/i18n.py +++ b/core/i18n.py @@ -111,6 +111,8 @@ _EN = { "term_connecting": "Connecting to {alias}...", "term_connected": "Connected to {alias}", "term_disconnected": "Disconnected", + "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})...", "term_connect_failed": "Connection failed: {error}", "term_reconnect_fail": "Disconnected (reconnect failed)", @@ -634,6 +636,8 @@ _RU = { "term_connecting": "Подключение к {alias}...", "term_connected": "Подключено к {alias}", "term_disconnected": "Отключено", + "term_click_to_connect": "Двойной клик для подключения к {alias}", + "sftp_click_to_connect": "Двойной клик для просмотра файлов", "term_reconnecting": "Переподключение ({n}/{max})...", "term_connect_failed": "Ошибка подключения: {error}", "term_reconnect_fail": "Отключено (не удалось переподключиться)", @@ -1157,6 +1161,8 @@ _ZH = { "term_connecting": "正在连接 {alias}...", "term_connected": "已连接到 {alias}", "term_disconnected": "已断开", + "term_click_to_connect": "双击连接 {alias}", + "sftp_click_to_connect": "双击服务器浏览文件", "term_reconnecting": "重新连接中 ({n}/{max})...", "term_connect_failed": "连接失败:{error}", "term_reconnect_fail": "已断开(重连失败)", diff --git a/gui/app.py b/gui/app.py index 6edfff8..550c89d 100644 --- a/gui/app.py +++ b/gui/app.py @@ -154,7 +154,7 @@ class App(ctk.CTk): self._paned.pack(fill="both", expand=True) # Sidebar - self.sidebar = Sidebar(self._paned, self.store, on_select=self._on_server_select, session_pool=self.session_pool) + self.sidebar = Sidebar(self._paned, self.store, on_select=self._on_server_select, on_double_click=self._on_server_connect, session_pool=self.session_pool) self._paned.add(self.sidebar, minsize=180, width=self.store._sidebar_width) self.sidebar.add_callback = self._add_server self.sidebar.edit_callback = self._edit_server @@ -307,6 +307,12 @@ class App(ctk.CTk): # Update session indicators after a short delay (connection is async) self.after(1500, self.sidebar.update_session_indicators) + def _on_server_connect(self, alias: str): + """Double-click: connect interactive tabs (terminal, files, powershell).""" + for key, widget in self._tab_instances.items(): + if hasattr(widget, "connect"): + widget.connect() + def _add_server(self): dialog = ServerDialog(self, self.store) self.wait_window(dialog) @@ -356,6 +362,10 @@ class App(ctk.CTk): self.tabview.set(_tab_label(tab_key)) except Exception: pass + # Connect the target tab if it supports explicit connection + widget = self._tab_instances.get(tab_key) + if widget and hasattr(widget, "connect"): + widget.connect() def _context_check_status(self, alias: str): """Context menu: check single server status in background.""" diff --git a/gui/sidebar.py b/gui/sidebar.py index 4caae3e..3bbbe36 100644 --- a/gui/sidebar.py +++ b/gui/sidebar.py @@ -34,10 +34,11 @@ _CONTEXT_ACTIONS = { class Sidebar(ctk.CTkFrame): - def __init__(self, master, store, on_select=None, session_pool=None): + def __init__(self, master, store, on_select=None, on_double_click=None, session_pool=None): super().__init__(master, width=250, corner_radius=0) self.store = store self.on_select = on_select + self.on_double_click = on_double_click self.session_pool = session_pool self._selected_alias: str | None = None self._server_frames: dict[str, ctk.CTkFrame] = {} @@ -272,6 +273,7 @@ class Sidebar(ctk.CTkFrame): # Click handlers for widget in [frame, info, name_label, detail_label, badge, type_badge, session_ind]: widget.bind("", lambda e, a=alias: self._select(a)) + widget.bind("", lambda e, a=alias: self._on_double_click(a)) widget.bind("", lambda e, a=alias: self._show_context_menu(e, a)) self._server_frames[alias] = frame @@ -371,6 +373,11 @@ class Sidebar(ctk.CTkFrame): if self.on_select: self.on_select(alias) + def _on_double_click(self, alias: str): + self._select(alias) + if self.on_double_click: + self.on_double_click(alias) + def _highlight_selected(self): for alias, frame in self._server_frames.items(): if alias == self._selected_alias: diff --git a/gui/tabs/files_tab.py b/gui/tabs/files_tab.py index 52ab481..47e2e5a 100644 --- a/gui/tabs/files_tab.py +++ b/gui/tabs/files_tab.py @@ -307,13 +307,17 @@ class FilesTab(ctk.CTkFrame): stored_path, stored_sudo = self.session_pool.get_sftp_state(alias) if stored_path != "/": self._remote_path = stored_path - # The stored sudo mode will be applied when the connection is established - self._connect_sftp() + self._remote_status.configure(text=t("sftp_click_to_connect")) else: self._remote_list.populate([]) self._remote_status.configure(text=t("connect_to_browse")) self._set_remote_buttons_state("disabled") + def connect(self): + """Explicitly connect SFTP (double-click or context menu).""" + if self._current_alias and not self._sftp: + self._connect_sftp() + # ── SFTP connection ── def _connect_sftp(self): diff --git a/gui/tabs/powershell_tab.py b/gui/tabs/powershell_tab.py index 1986a9d..d990774 100644 --- a/gui/tabs/powershell_tab.py +++ b/gui/tabs/powershell_tab.py @@ -97,7 +97,12 @@ class PowershellTab(ctk.CTkFrame): self._set_status(t("ps_disconnected"), "#888888") return - self._connect(alias) + self._set_status(t("term_click_to_connect").format(alias=alias), "#f59e0b") + + def connect(self): + """Explicitly connect WinRM (double-click or context menu).""" + if self._current_alias and not self._client: + self._connect(self._current_alias) # ── Connection ─────────────────────────────────────────────────── diff --git a/gui/tabs/terminal_tab.py b/gui/tabs/terminal_tab.py index 6259054..2877d1b 100644 --- a/gui/tabs/terminal_tab.py +++ b/gui/tabs/terminal_tab.py @@ -61,11 +61,16 @@ class TerminalTab(ctk.CTkFrame): self._current_alias = alias if alias: - self._connect() + self._terminal.set_status(t("term_click_to_connect").format(alias=alias), "#f59e0b") else: self._terminal.reset() self._terminal.set_status(t("term_disconnected"), "#888888") + def connect(self): + """Explicitly connect (double-click or context menu).""" + if self._current_alias and not self._session: + self._connect() + def _connect(self): if not self._current_alias: return diff --git a/plans/disable-terminal-autoconnect.md b/plans/disable-terminal-autoconnect.md new file mode 100644 index 0000000..8f8a6c2 --- /dev/null +++ b/plans/disable-terminal-autoconnect.md @@ -0,0 +1,160 @@ +# Отключить автоподключение терминала при одинарном клике + +## Контекст + +При одинарном клике на сервер в sidebar все табы (terminal, files, powershell) сразу подключаются к серверу. Пользователь хочет просто переключаться между серверами без автоподключения. Подключение — только по двойному клику. + +## Подход + +- **Одинарный клик** — выбрать сервер, обновить табы (info, setup, keys и т.д.), но НЕ подключаться к terminal/files/powershell +- **Двойной клик** — выбрать сервер + подключить все "connecting" табы (terminal, files, powershell) +- **Контекстное меню** "Open Terminal" / "Browse Files" — тоже подключает + +Tkinter при двойном клике генерирует оба события: `` (первый клик) → ``. Это нам на руку: первый клик выберет сервер, двойной клик — подключит. Debounce не нужен. + +## Изменения — 4 файла + +### 1. `gui/sidebar.py` — добавить двойной клик + +**Строка 37** — добавить `on_double_click` в конструктор: +```python +def __init__(self, master, store, on_select=None, on_double_click=None, session_pool=None): + ... + self.on_select = on_select + self.on_double_click = on_double_click +``` + +**Строки 272-275** — добавить `` binding: +```python +for widget in [frame, info, name_label, detail_label, badge, type_badge, session_ind]: + widget.bind("", lambda e, a=alias: self._select(a)) + widget.bind("", lambda e, a=alias: self._on_double_click(a)) + widget.bind("", lambda e, a=alias: self._show_context_menu(e, a)) +``` + +**После `_select()`** (строка 372) — новый метод: +```python +def _on_double_click(self, alias: str): + self._select(alias) + if self.on_double_click: + self.on_double_click(alias) +``` + +### 2. `gui/tabs/terminal_tab.py` — убрать автоподключение + +**Строки 49-67** — `set_server()`: заменить `self._connect()` на показ статуса: +```python +def set_server(self, alias: str | None): + if alias == self._current_alias: + return + if self._current_alias and self._session and self.session_pool: + buf = self._terminal.get_current_buffer() + self.session_pool.store_shell_state(self._current_alias, buf) + self._disconnect() + self._current_alias = alias + if alias: + self._terminal.set_status(t("term_click_to_connect").format(alias=alias), "#f59e0b") + else: + self._terminal.reset() + self._terminal.set_status(t("term_disconnected"), "#888888") +``` + +**Добавить публичный метод `connect()`** после `set_server()`: +```python +def connect(self): + """Explicitly connect (double-click or context menu).""" + if self._current_alias and not self._session: + self._connect() +``` + +### 3. `gui/tabs/files_tab.py` — убрать автоподключение + +**Строки 304-311** — `set_server()`: заменить `self._connect_sftp()` на статус: +```python +if alias: + if self.session_pool: + stored_path, stored_sudo = self.session_pool.get_sftp_state(alias) + if stored_path != "/": + self._remote_path = stored_path + self._remote_status.configure(text=t("sftp_click_to_connect")) +else: + ... +``` + +**Добавить публичный метод `connect()`**: +```python +def connect(self): + """Explicitly connect SFTP (double-click or context menu).""" + if self._current_alias and not self._sftp: + self._connect_sftp() +``` + +### 4. `gui/tabs/powershell_tab.py` — убрать автоподключение + +**Строка 100** — заменить `self._connect(alias)` на статус: +```python +if alias is None: + self._set_status(t("ps_disconnected"), "#888888") + return +self._set_status(t("term_click_to_connect").format(alias=alias), "#f59e0b") +``` + +**Добавить публичный метод `connect()`**: +```python +def connect(self): + """Explicitly connect WinRM (double-click or context menu).""" + if self._current_alias and not self._client: + self._connect(self._current_alias) +``` + +### 5. `gui/app.py` — подключить двойной клик + +**Строка 157** — передать `on_double_click`: +```python +self.sidebar = Sidebar(self._paned, self.store, + on_select=self._on_server_select, + on_double_click=self._on_server_connect, + session_pool=self.session_pool) +``` + +**Новый метод `_on_server_connect()`** (после `_on_server_select`): +```python +def _on_server_connect(self, alias: str): + """Double-click: connect interactive tabs (terminal, files, powershell).""" + for key, widget in self._tab_instances.items(): + if hasattr(widget, "connect"): + widget.connect() +``` + +**Строки 350-358** — `_context_open_tab()`: добавить вызов `connect()`: +```python +def _context_open_tab(self, alias: str, tab_key: str): + self._on_server_select(alias) + self.sidebar._select(alias) + if tab_key in self._tab_keys: + try: + self.tabview.set(_tab_label(tab_key)) + except Exception: + pass + # Connect the target tab if it supports explicit connection + widget = self._tab_instances.get(tab_key) + if widget and hasattr(widget, "connect"): + widget.connect() +``` + +### 6. `core/i18n.py` — 2 ключа перевода + +Рядом с `term_disconnected`: + +| Ключ | EN | RU | ZH | +|------|----|----|-----| +| `term_click_to_connect` | `Double-click to connect to {alias}` | `Двойной клик для подключения к {alias}` | `双击连接 {alias}` | +| `sftp_click_to_connect` | `Double-click server to browse files` | `Двойной клик для просмотра файлов` | `双击服务器浏览文件` | + +## Верификация + +1. `python build.py` — собрать exe +2. Запустить exe, одинарный клик на SSH-сервер → терминал показывает "Двойной клик для подключения", файлы показывают аналогичное сообщение, info таб работает как раньше +3. Двойной клик на сервер → терминал и файлы подключаются +4. Правый клик → "Open Terminal" → терминал подключается +5. Переключение между серверами одним кликом → нет автоподключений, быстрое переключение diff --git a/releases/ServerManager-v1.9.21-win-x64.exe b/releases/ServerManager-v1.9.26-win-x64.exe similarity index 98% rename from releases/ServerManager-v1.9.21-win-x64.exe rename to releases/ServerManager-v1.9.26-win-x64.exe index 3239eb8..9a102b2 100644 Binary files a/releases/ServerManager-v1.9.21-win-x64.exe and b/releases/ServerManager-v1.9.26-win-x64.exe differ diff --git a/version.py b/version.py index 63dbf1d..a118b47 100755 --- a/version.py +++ b/version.py @@ -1,6 +1,6 @@ """Version info for ServerManager.""" -__version__ = "1.9.25" +__version__ = "1.9.26" __app_name__ = "ServerManager" __author__ = "aibot777" __description__ = "Desktop GUI for managing remote servers"