From ded4aa04c7e5cc0aef5fdb5ae6ead4d75ac23bdb Mon Sep 17 00:00:00 2001 From: onysd Date: Fri, 31 Jul 2026 13:28:13 +0300 Subject: [PATCH] fix --- tui-panel/server-panel.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/tui-panel/server-panel.py b/tui-panel/server-panel.py index 5429698a..fb42445f 100644 --- a/tui-panel/server-panel.py +++ b/tui-panel/server-panel.py @@ -607,11 +607,18 @@ class CopyButton(Static): class LogTailScreen(Screen): - """Live-tails one or two log files. Escape/b goes back to the main menu.""" + """Live-tails one or two log files. Escape/b goes back to the main menu. + + RichLog renders through the terminal like everything else in this app, + so plain mouse-drag text selection doesn't reach the terminal -- Textual + captures the mouse for widget interaction instead. "c" copies everything + shown so far (all panes, with headers) as a workaround, using the same + clipboard/OSC 52 fallback as the CopyButton widgets.""" BINDINGS = [ Binding("escape", "back", "Back"), Binding("b", "back", "Back"), + Binding("c", "copy_logs", "Copy logs"), ] def __init__(self, panes: list[tuple[str, Path]]): @@ -619,6 +626,7 @@ class LogTailScreen(Screen): self._panes = panes self._offsets: dict[Path, int] = {path: 0 for _, path in panes} self._logs: dict[Path, RichLog] = {} + self._buffers: dict[Path, list[str]] = {path: [] for _, path in panes} def compose(self) -> ComposeResult: yield Header() @@ -649,6 +657,7 @@ class LogTailScreen(Screen): text = data.decode("utf-8", errors="replace") if text: self._logs[path].write(text) + self._buffers[path].append(text) def _poll(self) -> None: for _, path in self._panes: @@ -664,11 +673,25 @@ class LogTailScreen(Screen): f.seek(offset) data = f.read() self._offsets[path] = size - self._logs[path].write(data.decode("utf-8", errors="replace")) + text = data.decode("utf-8", errors="replace") + self._logs[path].write(text) + self._buffers[path].append(text) def action_back(self) -> None: self.app.pop_screen() + def action_copy_logs(self) -> None: + parts = [] + for title, path in self._panes: + content = "".join(self._buffers[path]).strip() + parts.append(f"--- {title} ({path.name}) ---\n{content or '(no logs yet)'}") + text = "\n\n".join(parts) + if copy_to_clipboard(text): + self.notify("Logs copied to clipboard") + return + self.app.copy_to_clipboard(text) + self.notify("Logs copied to clipboard") + class LogPickerScreen(Screen): BINDINGS = [Binding("escape", "back", "Back")] @@ -803,7 +826,12 @@ SETUP_FIELDS: list[tuple[str, list[SetupField]]] = [ SetupField( "TELESRV_ADVERTISE_IP", "Server public IP or hostname", "What clients use to reach this server -- your VPS's public IP, " - "or a domain name if you have one pointed at it.", + "or a domain name if you have one pointed at it. Calls (TURN/SFU) " + "are the one exception: they need a literal IP address, not a " + "hostname, and default to reusing this value -- if you enter a " + "hostname here and want calls to work, set " + "TELESRV_TURN_ADVERTISE_IP / TELESRV_SFU_ADVERTISE_IP to a real " + "IP separately afterward, from Configure .env.", required=True, ), ]),