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:
13
bin/gniza
13
bin/gniza
@@ -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
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,6 +28,8 @@ 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")
|
||||||
|
with Horizontal(id="snapshots-buttons"):
|
||||||
|
yield Button("Browse Files", id="btn-browse")
|
||||||
yield Button("Back", id="btn-back")
|
yield Button("Back", id="btn-back")
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user