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 <noreply@anthropic.com>
This commit is contained in:
shuki
2026-03-06 05:08:45 +02:00
parent d6032d2547
commit e73f38df53
3 changed files with 57 additions and 3 deletions

View File

@@ -305,8 +305,19 @@ run_cli() {
fi fi
_restore_remote_globals _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 esac
;; ;;

View File

@@ -111,6 +111,7 @@ SelectionList {
#targets-buttons, #targets-buttons,
#remotes-buttons, #remotes-buttons,
#logs-buttons, #logs-buttons,
#snapshots-buttons,
#sched-buttons, #sched-buttons,
#sched-edit-buttons, #sched-edit-buttons,
#te-buttons, #te-buttons,
@@ -126,6 +127,7 @@ SelectionList {
#targets-buttons Button, #targets-buttons Button,
#remotes-buttons Button, #remotes-buttons Button,
#logs-buttons Button, #logs-buttons Button,
#snapshots-buttons Button,
#sched-buttons Button, #sched-buttons Button,
#sched-edit-buttons Button, #sched-edit-buttons Button,
#te-buttons Button, #te-buttons Button,

View File

@@ -6,6 +6,7 @@ from textual import work
from tui.config import list_conf_dir from tui.config import list_conf_dir
from tui.backend import run_cli from tui.backend import run_cli
from tui.widgets import OperationLog
class SnapshotsScreen(Screen): 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 Select([(r, r) for r in remotes], id="snap-remote", prompt="Select remote")
yield Button("Load Snapshots", id="btn-load", variant="primary") yield Button("Load Snapshots", id="btn-load", variant="primary")
yield DataTable(id="snap-table") 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() yield Footer()
def on_mount(self) -> None: def on_mount(self) -> None:
@@ -42,6 +45,19 @@ class SnapshotsScreen(Screen):
self.app.pop_screen() self.app.pop_screen()
elif event.button.id == "btn-load": elif event.button.id == "btn-load":
self._load_snapshots() 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 @work
async def _load_snapshots(self) -> None: 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("===")] lines = [l.strip() for l in stdout.splitlines() if l.strip() and not l.startswith("===")]
if lines: if lines:
for s in lines: for s in lines:
table.add_row(s) table.add_row(s, key=s)
else: else:
self.notify("No snapshots found", severity="warning") self.notify("No snapshots found", severity="warning")
if stderr: if stderr:
self.notify(stderr.strip(), severity="error") 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: def action_go_back(self) -> None:
self.app.pop_screen() self.app.pop_screen()