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:
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user