Fix OperationLog not rendering by removing LoadingIndicator
LoadingIndicator was causing OperationLog ModalScreen to fail silently during compose. Replaced with a simple Static emoji spinner (⏳→✅). Reverted screen push patterns back to simple callback approach. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -57,19 +57,26 @@ class BackupScreen(Screen):
|
||||
msg += f"\nRemote: {remote}"
|
||||
self.app.push_screen(
|
||||
ConfirmDialog(msg, "Confirm Backup"),
|
||||
callback=lambda ok: self._do_backup(target, remote) if ok else None,
|
||||
callback=lambda ok: self._confirmed_backup(target, remote) if ok else None,
|
||||
)
|
||||
elif event.button.id == "btn-backup-all":
|
||||
self.app.push_screen(
|
||||
ConfirmDialog("Backup ALL targets now?", "Confirm Backup"),
|
||||
callback=lambda ok: self._do_backup_all() if ok else None,
|
||||
callback=lambda ok: self._confirmed_backup_all() if ok else None,
|
||||
)
|
||||
|
||||
@work
|
||||
async def _do_backup(self, target: str, remote: str) -> None:
|
||||
def _confirmed_backup(self, target: str, remote: str) -> None:
|
||||
log_screen = OperationLog(f"Backup: {target}")
|
||||
self.app.push_screen(log_screen)
|
||||
await log_screen.wait_ready()
|
||||
self._run_backup(log_screen, target, remote)
|
||||
|
||||
def _confirmed_backup_all(self) -> None:
|
||||
log_screen = OperationLog("Backup All Targets")
|
||||
self.app.push_screen(log_screen)
|
||||
self._run_backup_all(log_screen)
|
||||
|
||||
@work
|
||||
async def _run_backup(self, log_screen: OperationLog, target: str, remote: str) -> None:
|
||||
args = ["backup", f"--target={target}"]
|
||||
if remote:
|
||||
args.append(f"--remote={remote}")
|
||||
@@ -81,10 +88,7 @@ class BackupScreen(Screen):
|
||||
log_screen.finish()
|
||||
|
||||
@work
|
||||
async def _do_backup_all(self) -> None:
|
||||
log_screen = OperationLog("Backup All Targets")
|
||||
self.app.push_screen(log_screen)
|
||||
await log_screen.wait_ready()
|
||||
async def _run_backup_all(self, log_screen: OperationLog) -> None:
|
||||
rc = await stream_cli(log_screen.write, "backup", "--all")
|
||||
if rc == 0:
|
||||
log_screen.write("\n[green]All backups completed.[/green]")
|
||||
|
||||
@@ -81,11 +81,13 @@ class RemotesScreen(Screen):
|
||||
else:
|
||||
self.notify("Select a remote first", severity="warning")
|
||||
|
||||
@work
|
||||
async def _test_remote(self, name: str) -> None:
|
||||
def _test_remote(self, name: str) -> None:
|
||||
log_screen = OperationLog(f"Testing Remote: {name}")
|
||||
self.app.push_screen(log_screen)
|
||||
await log_screen.wait_ready()
|
||||
self._run_test_remote(log_screen, name)
|
||||
|
||||
@work
|
||||
async def _run_test_remote(self, log_screen: OperationLog, name: str) -> None:
|
||||
rc, stdout, stderr = await run_cli("remotes", "test", f"--name={name}")
|
||||
if stdout:
|
||||
log_screen.write(stdout)
|
||||
|
||||
@@ -107,14 +107,16 @@ class RestoreScreen(Screen):
|
||||
msg += "\nLocation: In-place"
|
||||
self.app.push_screen(
|
||||
ConfirmDialog(msg, "Confirm Restore"),
|
||||
callback=lambda ok: self._do_restore(target, remote, snapshot, dest) if ok else None,
|
||||
callback=lambda ok: self._confirmed_restore(target, remote, snapshot, dest) if ok else None,
|
||||
)
|
||||
|
||||
@work
|
||||
async def _do_restore(self, target: str, remote: str, snapshot: str, dest: str) -> None:
|
||||
def _confirmed_restore(self, target: str, remote: str, snapshot: str, dest: str) -> None:
|
||||
log_screen = OperationLog(f"Restore: {target}")
|
||||
self.app.push_screen(log_screen)
|
||||
await log_screen.wait_ready()
|
||||
self._run_restore(log_screen, target, remote, snapshot, dest)
|
||||
|
||||
@work
|
||||
async def _run_restore(self, log_screen: OperationLog, target: str, remote: str, snapshot: str, dest: str) -> None:
|
||||
args = ["restore", f"--target={target}", f"--remote={remote}", f"--snapshot={snapshot}"]
|
||||
if dest:
|
||||
args.append(f"--dest={dest}")
|
||||
|
||||
@@ -51,12 +51,12 @@ class RetentionScreen(Screen):
|
||||
target = str(target_sel.value)
|
||||
self.app.push_screen(
|
||||
ConfirmDialog(f"Run retention cleanup for '{target}'?", "Confirm"),
|
||||
callback=lambda ok: self._do_cleanup(target) if ok else None,
|
||||
callback=lambda ok: self._confirmed_cleanup(target) if ok else None,
|
||||
)
|
||||
elif event.button.id == "btn-cleanup-all":
|
||||
self.app.push_screen(
|
||||
ConfirmDialog("Run retention cleanup for ALL targets?", "Confirm"),
|
||||
callback=lambda ok: self._do_cleanup_all() if ok else None,
|
||||
callback=lambda ok: self._confirmed_cleanup_all() if ok else None,
|
||||
)
|
||||
elif event.button.id == "btn-save-count":
|
||||
val = self.query_one("#ret-count", Input).value.strip()
|
||||
@@ -66,11 +66,18 @@ class RetentionScreen(Screen):
|
||||
update_conf_key(CONFIG_DIR / "gniza.conf", "RETENTION_COUNT", val)
|
||||
self.notify(f"Retention count set to {val}.")
|
||||
|
||||
@work
|
||||
async def _do_cleanup(self, target: str) -> None:
|
||||
def _confirmed_cleanup(self, target: str) -> None:
|
||||
log_screen = OperationLog(f"Retention: {target}")
|
||||
self.app.push_screen(log_screen)
|
||||
await log_screen.wait_ready()
|
||||
self._run_cleanup(log_screen, target)
|
||||
|
||||
def _confirmed_cleanup_all(self) -> None:
|
||||
log_screen = OperationLog("Retention: All Targets")
|
||||
self.app.push_screen(log_screen)
|
||||
self._run_cleanup_all(log_screen)
|
||||
|
||||
@work
|
||||
async def _run_cleanup(self, log_screen: OperationLog, target: str) -> None:
|
||||
rc = await stream_cli(log_screen.write, "retention", f"--target={target}")
|
||||
if rc == 0:
|
||||
log_screen.write("\n[green]Cleanup completed.[/green]")
|
||||
@@ -79,10 +86,7 @@ class RetentionScreen(Screen):
|
||||
log_screen.finish()
|
||||
|
||||
@work
|
||||
async def _do_cleanup_all(self) -> None:
|
||||
log_screen = OperationLog("Retention: All Targets")
|
||||
self.app.push_screen(log_screen)
|
||||
await log_screen.wait_ready()
|
||||
async def _run_cleanup_all(self, log_screen: OperationLog) -> None:
|
||||
rc = await stream_cli(log_screen.write, "retention", "--all")
|
||||
if rc == 0:
|
||||
log_screen.write("\n[green]All cleanups completed.[/green]")
|
||||
|
||||
@@ -216,11 +216,13 @@ class ScheduleScreen(Screen):
|
||||
self.notify(f"Schedule '{name}' deleted.")
|
||||
self._refresh_table()
|
||||
|
||||
@work
|
||||
async def _install_schedules(self) -> None:
|
||||
def _install_schedules(self) -> None:
|
||||
log_screen = OperationLog("Install Schedules")
|
||||
self.app.push_screen(log_screen)
|
||||
await log_screen.wait_ready()
|
||||
self._run_install(log_screen)
|
||||
|
||||
@work
|
||||
async def _run_install(self, log_screen: OperationLog) -> None:
|
||||
rc, stdout, stderr = await run_cli("schedule", "install")
|
||||
if stdout:
|
||||
log_screen.write(stdout)
|
||||
@@ -228,11 +230,13 @@ class ScheduleScreen(Screen):
|
||||
log_screen.write(stderr)
|
||||
log_screen.finish()
|
||||
|
||||
@work
|
||||
async def _remove_schedules(self) -> None:
|
||||
def _remove_schedules(self) -> None:
|
||||
log_screen = OperationLog("Remove Schedules")
|
||||
self.app.push_screen(log_screen)
|
||||
await log_screen.wait_ready()
|
||||
self._run_remove(log_screen)
|
||||
|
||||
@work
|
||||
async def _run_remove(self, log_screen: OperationLog) -> None:
|
||||
rc, stdout, stderr = await run_cli("schedule", "remove")
|
||||
if stdout:
|
||||
log_screen.write(stdout)
|
||||
@@ -240,11 +244,13 @@ class ScheduleScreen(Screen):
|
||||
log_screen.write(stderr)
|
||||
log_screen.finish()
|
||||
|
||||
@work
|
||||
async def _show_crontab(self) -> None:
|
||||
def _show_crontab(self) -> None:
|
||||
log_screen = OperationLog("Current Crontab")
|
||||
self.app.push_screen(log_screen)
|
||||
await log_screen.wait_ready()
|
||||
self._run_show(log_screen)
|
||||
|
||||
@work
|
||||
async def _run_show(self, log_screen: OperationLog) -> None:
|
||||
rc, stdout, stderr = await run_cli("schedule", "show")
|
||||
if stdout:
|
||||
log_screen.write(stdout)
|
||||
|
||||
@@ -3,7 +3,7 @@ import asyncio
|
||||
from rich.text import Text
|
||||
from textual.app import ComposeResult
|
||||
from textual.screen import ModalScreen
|
||||
from textual.widgets import RichLog, Button, Static, LoadingIndicator
|
||||
from textual.widgets import RichLog, Button, Static
|
||||
from textual.containers import Vertical, Horizontal
|
||||
|
||||
|
||||
@@ -14,26 +14,16 @@ class OperationLog(ModalScreen[None]):
|
||||
def __init__(self, title: str = "Operation Output"):
|
||||
super().__init__()
|
||||
self._title = title
|
||||
self._mounted_event = asyncio.Event()
|
||||
self._buffer: list[str] = []
|
||||
self._running = True
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Vertical(id="op-log"):
|
||||
with Horizontal(id="ol-header"):
|
||||
yield Static(self._title, id="ol-title")
|
||||
yield LoadingIndicator(id="ol-spinner")
|
||||
yield Static("⏳", id="ol-spinner")
|
||||
yield RichLog(id="ol-log", wrap=True, highlight=True, markup=True)
|
||||
yield Button("Close", variant="primary", id="ol-close")
|
||||
|
||||
def on_mount(self) -> None:
|
||||
# Flush any buffered writes
|
||||
log = self.query_one("#ol-log", RichLog)
|
||||
for text in self._buffer:
|
||||
self._write_to_log(log, text)
|
||||
self._buffer.clear()
|
||||
self._mounted_event.set()
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
self.dismiss(None)
|
||||
|
||||
@@ -46,22 +36,16 @@ class OperationLog(ModalScreen[None]):
|
||||
else:
|
||||
log.write(text)
|
||||
|
||||
async def wait_ready(self) -> None:
|
||||
await self._mounted_event.wait()
|
||||
|
||||
def finish(self) -> None:
|
||||
self._running = False
|
||||
try:
|
||||
self.query_one("#ol-spinner", LoadingIndicator).display = False
|
||||
self.query_one("#ol-spinner", Static).update("✅")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def write(self, text: str) -> None:
|
||||
if not self._mounted_event.is_set():
|
||||
self._buffer.append(text)
|
||||
return
|
||||
try:
|
||||
log = self.query_one("#ol-log", RichLog)
|
||||
self._write_to_log(log, text)
|
||||
except Exception:
|
||||
self._buffer.append(text)
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user