Revert OperationLog and all screens to pre-spinner working state

Reverts all spinner/loading indicator changes and debug code.
Restores the exact code from commit 0e02ba6 which was confirmed working.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
shuki
2026-03-06 04:25:57 +02:00
parent 75259c9d34
commit ced2e9a889
8 changed files with 59 additions and 102 deletions

View File

@@ -193,21 +193,10 @@ SelectionList {
border: thick $accent;
}
#ol-header {
height: auto;
margin: 0 0 1 0;
}
#ol-title {
text-style: bold;
color: #00cc00;
width: 1fr;
}
#ol-spinner {
width: auto;
min-width: 4;
height: 1;
margin: 0 0 1 0;
}
#ol-log {

View File

@@ -42,8 +42,6 @@ class BackupScreen(Screen):
yield Footer()
def on_button_pressed(self, event: Button.Pressed) -> None:
with open("/tmp/gniza_debug.log", "a") as f:
f.write(f"on_button_pressed: {event.button.id}\n")
if event.button.id == "btn-back":
self.app.pop_screen()
elif event.button.id == "btn-backup":
@@ -54,38 +52,23 @@ class BackupScreen(Screen):
target = str(target_sel.value)
remote_sel = self.query_one("#backup-remote", Select)
remote = str(remote_sel.value) if isinstance(remote_sel.value, str) else ""
# Skip ConfirmDialog — go straight to OperationLog for debugging
self._confirmed_backup(target, remote)
msg = f"Run backup for target '{target}'?"
if remote:
msg += f"\nRemote: {remote}"
self.app.push_screen(
ConfirmDialog(msg, "Confirm Backup"),
callback=lambda ok: self._do_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._confirmed_backup_all() if ok else None,
callback=lambda ok: self._do_backup_all() if ok else None,
)
def _confirmed_backup(self, target: str, remote: str) -> None:
import traceback
dbg = open("/tmp/gniza_debug.log", "a")
dbg.write("=== _confirmed_backup called ===\n")
dbg.write(f"target={target} remote={remote}\n")
try:
log_screen = OperationLog(f"Backup: {target}")
dbg.write("OperationLog created\n")
self.app.push_screen(log_screen)
dbg.write("push_screen called\n")
self._run_backup(log_screen, target, remote)
dbg.write("_run_backup started\n")
except Exception as e:
dbg.write(f"ERROR: {e}\n")
dbg.write(traceback.format_exc())
dbg.close()
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:
async def _do_backup(self, target: str, remote: str) -> None:
log_screen = OperationLog(f"Backup: {target}")
self.app.push_screen(log_screen)
args = ["backup", f"--target={target}"]
if remote:
args.append(f"--remote={remote}")
@@ -94,16 +77,16 @@ 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 _run_backup_all(self, log_screen: OperationLog) -> None:
async def _do_backup_all(self) -> None:
log_screen = OperationLog("Backup All Targets")
self.app.push_screen(log_screen)
rc = await stream_cli(log_screen.write, "backup", "--all")
if rc == 0:
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()

View File

@@ -81,13 +81,10 @@ class RemotesScreen(Screen):
else:
self.notify("Select a remote first", severity="warning")
def _test_remote(self, name: str) -> None:
@work
async def _test_remote(self, name: str) -> None:
log_screen = OperationLog(f"Testing Remote: {name}")
self.app.push_screen(log_screen)
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)
@@ -97,7 +94,6 @@ 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"

View File

@@ -107,16 +107,13 @@ class RestoreScreen(Screen):
msg += "\nLocation: In-place"
self.app.push_screen(
ConfirmDialog(msg, "Confirm Restore"),
callback=lambda ok: self._confirmed_restore(target, remote, snapshot, dest) if ok else None,
callback=lambda ok: self._do_restore(target, remote, snapshot, dest) if ok else None,
)
def _confirmed_restore(self, target: str, remote: str, snapshot: str, dest: str) -> None:
@work
async def _do_restore(self, target: str, remote: str, snapshot: str, dest: str) -> None:
log_screen = OperationLog(f"Restore: {target}")
self.app.push_screen(log_screen)
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}")
@@ -125,7 +122,6 @@ 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()

View File

@@ -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._confirmed_cleanup(target) if ok else None,
callback=lambda ok: self._do_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._confirmed_cleanup_all() if ok else None,
callback=lambda ok: self._do_cleanup_all() if ok else None,
)
elif event.button.id == "btn-save-count":
val = self.query_one("#ret-count", Input).value.strip()
@@ -66,33 +66,25 @@ class RetentionScreen(Screen):
update_conf_key(CONFIG_DIR / "gniza.conf", "RETENTION_COUNT", val)
self.notify(f"Retention count set to {val}.")
def _confirmed_cleanup(self, target: str) -> None:
@work
async def _do_cleanup(self, target: str) -> None:
log_screen = OperationLog(f"Retention: {target}")
self.app.push_screen(log_screen)
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]")
else:
log_screen.write(f"\n[red]Cleanup failed (exit code {rc}).[/red]")
log_screen.finish()
@work
async def _run_cleanup_all(self, log_screen: OperationLog) -> None:
async def _do_cleanup_all(self) -> None:
log_screen = OperationLog("Retention: All Targets")
self.app.push_screen(log_screen)
rc = await stream_cli(log_screen.write, "retention", "--all")
if rc == 0:
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()

View File

@@ -216,47 +216,35 @@ class ScheduleScreen(Screen):
self.notify(f"Schedule '{name}' deleted.")
self._refresh_table()
def _install_schedules(self) -> None:
@work
async def _install_schedules(self) -> None:
log_screen = OperationLog("Install Schedules")
self.app.push_screen(log_screen)
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)
if stderr:
log_screen.write(stderr)
log_screen.finish()
def _remove_schedules(self) -> None:
log_screen = OperationLog("Remove Schedules")
self.app.push_screen(log_screen)
self._run_remove(log_screen)
@work
async def _run_remove(self, log_screen: OperationLog) -> None:
async def _remove_schedules(self) -> None:
log_screen = OperationLog("Remove Schedules")
self.app.push_screen(log_screen)
rc, stdout, stderr = await run_cli("schedule", "remove")
if stdout:
log_screen.write(stdout)
if stderr:
log_screen.write(stderr)
log_screen.finish()
def _show_crontab(self) -> None:
log_screen = OperationLog("Current Crontab")
self.app.push_screen(log_screen)
self._run_show(log_screen)
@work
async def _run_show(self, log_screen: OperationLog) -> None:
async def _show_crontab(self) -> None:
log_screen = OperationLog("Current Crontab")
self.app.push_screen(log_screen)
rc, stdout, stderr = await run_cli("schedule", "show")
if stdout:
log_screen.write(stdout)
if stderr:
log_screen.write(stderr)
log_screen.finish()
def action_go_back(self) -> None:
self.app.pop_screen()

View File

@@ -22,7 +22,6 @@ class ConfirmDialog(ModalScreen[bool]):
yield Button("No", variant="default", id="cd-no")
def on_button_pressed(self, event: Button.Pressed) -> None:
self.notify(f"DEBUG CD: button={event.button.id}, dismissing with {event.button.id == 'cd-yes'}")
self.dismiss(event.button.id == "cd-yes")
def action_cancel(self) -> None:

View File

@@ -1,3 +1,5 @@
import asyncio
from rich.text import Text
from textual.app import ComposeResult
from textual.screen import ModalScreen
@@ -12,29 +14,41 @@ class OperationLog(ModalScreen[None]):
def __init__(self, title: str = "Operation Output"):
super().__init__()
self._title = title
self._running = True
self._mounted_event = asyncio.Event()
self._buffer: list[str] = []
def compose(self) -> ComposeResult:
with Vertical(id="op-log"):
yield Static(self._title, id="ol-title")
yield Static("Running...", id="ol-status")
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)
def action_close(self) -> None:
self.dismiss(None)
def finish(self) -> None:
self._running = False
try:
self.query_one("#ol-status", Static).update("Done ✅")
except Exception:
pass
def _write_to_log(self, log: RichLog, text: str) -> None:
if "[" in text and "[/" in text:
log.write(Text.from_markup(text))
else:
log.write(text)
def write(self, text: str) -> None:
if not self._mounted_event.is_set():
self._buffer.append(text)
return
try:
self.query_one("#ol-status", Static).update(text)
log = self.query_one("#ol-log", RichLog)
self._write_to_log(log, text)
except Exception:
pass
self._buffer.append(text)