diff --git a/tui/gniza.tcss b/tui/gniza.tcss index c511c2d..5a83b92 100644 --- a/tui/gniza.tcss +++ b/tui/gniza.tcss @@ -193,10 +193,21 @@ SelectionList { border: thick $accent; } +#ol-header { + height: auto; + margin: 0 0 1 0; +} + #ol-title { text-style: bold; color: #00cc00; - margin: 0 0 1 0; + width: 1fr; +} + +#ol-spinner { + width: auto; + min-width: 4; + height: 1; } #ol-log { diff --git a/tui/screens/backup.py b/tui/screens/backup.py index a927c0a..77f90ce 100644 --- a/tui/screens/backup.py +++ b/tui/screens/backup.py @@ -77,6 +77,7 @@ class BackupScreen(Screen): log_screen.write("\n[green]Backup completed successfully.[/green]") else: log_screen.write(f"\n[red]Backup failed (exit code {rc}).[/red]") + log_screen.finish() @work async def _do_backup_all(self) -> None: @@ -87,6 +88,7 @@ class BackupScreen(Screen): log_screen.write("\n[green]All backups completed.[/green]") else: log_screen.write(f"\n[red]Backup failed (exit code {rc}).[/red]") + log_screen.finish() def action_go_back(self) -> None: self.app.pop_screen() diff --git a/tui/screens/remotes.py b/tui/screens/remotes.py index e742b4e..2666c0b 100644 --- a/tui/screens/remotes.py +++ b/tui/screens/remotes.py @@ -94,6 +94,7 @@ class RemotesScreen(Screen): log_screen.write("\n[green]Connection test passed.[/green]") else: log_screen.write(f"\n[red]Connection test failed (exit code {rc}).[/red]") + log_screen.finish() def _delete_remote(self, name: str) -> None: conf = CONFIG_DIR / "remotes.d" / f"{name}.conf" diff --git a/tui/screens/restore.py b/tui/screens/restore.py index 9b186c7..25ef505 100644 --- a/tui/screens/restore.py +++ b/tui/screens/restore.py @@ -122,6 +122,7 @@ class RestoreScreen(Screen): log_screen.write("\n[green]Restore completed successfully.[/green]") else: log_screen.write(f"\n[red]Restore failed (exit code {rc}).[/red]") + log_screen.finish() def action_go_back(self) -> None: self.app.pop_screen() diff --git a/tui/screens/retention.py b/tui/screens/retention.py index 0a99d96..25ee310 100644 --- a/tui/screens/retention.py +++ b/tui/screens/retention.py @@ -75,6 +75,7 @@ class RetentionScreen(Screen): log_screen.write("\n[green]Cleanup completed.[/green]") else: log_screen.write(f"\n[red]Cleanup failed (exit code {rc}).[/red]") + log_screen.finish() @work async def _do_cleanup_all(self) -> None: @@ -85,6 +86,7 @@ class RetentionScreen(Screen): log_screen.write("\n[green]All cleanups completed.[/green]") else: log_screen.write(f"\n[red]Cleanup failed (exit code {rc}).[/red]") + log_screen.finish() def action_go_back(self) -> None: self.app.pop_screen() diff --git a/tui/screens/schedule.py b/tui/screens/schedule.py index db0a06d..88e722f 100644 --- a/tui/screens/schedule.py +++ b/tui/screens/schedule.py @@ -225,6 +225,7 @@ class ScheduleScreen(Screen): log_screen.write(stdout) if stderr: log_screen.write(stderr) + log_screen.finish() @work async def _remove_schedules(self) -> None: @@ -235,6 +236,7 @@ class ScheduleScreen(Screen): log_screen.write(stdout) if stderr: log_screen.write(stderr) + log_screen.finish() @work async def _show_crontab(self) -> None: @@ -245,6 +247,7 @@ class ScheduleScreen(Screen): log_screen.write(stdout) if stderr: log_screen.write(stderr) + log_screen.finish() def action_go_back(self) -> None: self.app.pop_screen() diff --git a/tui/widgets/operation_log.py b/tui/widgets/operation_log.py index 1b1176a..e644087 100644 --- a/tui/widgets/operation_log.py +++ b/tui/widgets/operation_log.py @@ -3,8 +3,8 @@ import asyncio from rich.text import Text from textual.app import ComposeResult from textual.screen import ModalScreen -from textual.widgets import RichLog, Button, Static -from textual.containers import Vertical +from textual.widgets import RichLog, Button, Static, LoadingIndicator +from textual.containers import Vertical, Horizontal class OperationLog(ModalScreen[None]): @@ -16,10 +16,13 @@ class OperationLog(ModalScreen[None]): self._title = title self._mounted_event = asyncio.Event() self._buffer: list[str] = [] + self._running = True def compose(self) -> ComposeResult: with Vertical(id="op-log"): - yield Static(self._title, id="ol-title") + with Horizontal(id="ol-header"): + yield Static(self._title, id="ol-title") + yield LoadingIndicator(id="ol-spinner") yield RichLog(id="ol-log", wrap=True, highlight=True, markup=True) yield Button("Close", variant="primary", id="ol-close") @@ -43,6 +46,13 @@ class OperationLog(ModalScreen[None]): else: log.write(text) + def finish(self) -> None: + self._running = False + try: + self.query_one("#ol-spinner", LoadingIndicator).display = False + except Exception: + pass + def write(self, text: str) -> None: if not self._mounted_event.is_set(): self._buffer.append(text)