From e73f38df535b51d31654c580473a85e194ba985a Mon Sep 17 00:00:00 2001 From: shuki Date: Fri, 6 Mar 2026 05:08:45 +0200 Subject: [PATCH] Add Browse Files button to snapshots screen Select a snapshot and click Browse Files to see all files in that snapshot on the remote. Also adds 'gniza snapshots browse' CLI command. Co-Authored-By: Claude Opus 4.6 --- bin/gniza | 13 +++++++++++- tui/gniza.tcss | 2 ++ tui/screens/snapshots.py | 45 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/bin/gniza b/bin/gniza index a1534a3..db50eae 100755 --- a/bin/gniza +++ b/bin/gniza @@ -305,8 +305,19 @@ run_cli() { fi _restore_remote_globals ;; + browse) + [[ -z "$target" ]] && die "browse requires --target=NAME" + local snapshot="" + snapshot=$(_parse_flag "--snapshot" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true + [[ -z "$snapshot" ]] && die "browse requires --snapshot=TS" + if [[ -z "$remote" ]]; then + remote=$(list_remotes | head -1) + [[ -z "$remote" ]] && die "No remotes configured" + fi + list_snapshot_contents "$target" "$snapshot" "$remote" + ;; *) - die "Unknown snapshots action: $action (expected list)" + die "Unknown snapshots action: $action (expected list|browse)" ;; esac ;; diff --git a/tui/gniza.tcss b/tui/gniza.tcss index 28fedc0..fe38b9b 100644 --- a/tui/gniza.tcss +++ b/tui/gniza.tcss @@ -111,6 +111,7 @@ SelectionList { #targets-buttons, #remotes-buttons, #logs-buttons, +#snapshots-buttons, #sched-buttons, #sched-edit-buttons, #te-buttons, @@ -126,6 +127,7 @@ SelectionList { #targets-buttons Button, #remotes-buttons Button, #logs-buttons Button, +#snapshots-buttons Button, #sched-buttons Button, #sched-edit-buttons Button, #te-buttons Button, diff --git a/tui/screens/snapshots.py b/tui/screens/snapshots.py index 49e82a9..4c02f63 100644 --- a/tui/screens/snapshots.py +++ b/tui/screens/snapshots.py @@ -6,6 +6,7 @@ from textual import work from tui.config import list_conf_dir from tui.backend import run_cli +from tui.widgets import OperationLog class SnapshotsScreen(Screen): @@ -27,7 +28,9 @@ class SnapshotsScreen(Screen): yield Select([(r, r) for r in remotes], id="snap-remote", prompt="Select remote") yield Button("Load Snapshots", id="btn-load", variant="primary") yield DataTable(id="snap-table") - yield Button("Back", id="btn-back") + with Horizontal(id="snapshots-buttons"): + yield Button("Browse Files", id="btn-browse") + yield Button("Back", id="btn-back") yield Footer() def on_mount(self) -> None: @@ -42,6 +45,19 @@ class SnapshotsScreen(Screen): self.app.pop_screen() elif event.button.id == "btn-load": self._load_snapshots() + elif event.button.id == "btn-browse": + self._browse_snapshot() + + def _selected_snapshot(self) -> str | None: + try: + table = self.query_one("#snap-table", DataTable) + if table.cursor_row is not None and table.row_count > 0: + row_key = table.coordinate_to_cell_key((table.cursor_row, 0)).row_key.value + # The snapshot name is in the first (only) column + return str(table.get_cell(row_key, "Snapshot")) + return None + except Exception: + return None @work async def _load_snapshots(self) -> None: @@ -58,11 +74,36 @@ class SnapshotsScreen(Screen): lines = [l.strip() for l in stdout.splitlines() if l.strip() and not l.startswith("===")] if lines: for s in lines: - table.add_row(s) + table.add_row(s, key=s) else: self.notify("No snapshots found", severity="warning") if stderr: self.notify(stderr.strip(), severity="error") + @work + async def _browse_snapshot(self) -> None: + target_sel = self.query_one("#snap-target", Select) + remote_sel = self.query_one("#snap-remote", Select) + if not isinstance(target_sel.value, str) or not isinstance(remote_sel.value, str): + self.notify("Select target and remote first", severity="error") + return + snapshot = self._selected_snapshot() + if not snapshot: + self.notify("Select a snapshot first", severity="warning") + return + target = str(target_sel.value) + remote = str(remote_sel.value) + log_screen = OperationLog(f"Files: {target}/{snapshot}", show_spinner=False) + self.app.push_screen(log_screen) + rc, stdout, stderr = await run_cli( + "snapshots", "browse", f"--target={target}", f"--remote={remote}", f"--snapshot={snapshot}" + ) + if stdout: + log_screen.write(stdout) + if stderr: + log_screen.write(stderr) + if not stdout and not stderr: + log_screen.write("No files found.") + def action_go_back(self) -> None: self.app.pop_screen()