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