From 8b3d6ede4feb7503d05ee03e577430432033502e Mon Sep 17 00:00:00 2001 From: shuki Date: Fri, 6 Mar 2026 06:44:27 +0200 Subject: [PATCH] Show disk usage in remotes table with async loading Co-Authored-By: Claude Opus 4.6 --- bin/gniza | 5 +++++ lib/remotes.sh | 25 +++++++++++++++++++++++++ tui/screens/remotes.py | 18 ++++++++++++++++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/bin/gniza b/bin/gniza index df11a4a..463167a 100755 --- a/bin/gniza +++ b/bin/gniza @@ -274,6 +274,11 @@ run_cli() { validate_remote "$name" echo "Remote '$name' is valid." ;; + disk-info-short) + [[ -z "$name" ]] && die "remotes disk-info-short requires --name=NAME" + load_remote "$name" + remote_disk_info_short + ;; disk-info) [[ -z "$name" ]] && die "remotes disk-info requires --name=NAME" load_remote "$name" diff --git a/lib/remotes.sh b/lib/remotes.sh index 2b070d9..244bc48 100644 --- a/lib/remotes.sh +++ b/lib/remotes.sh @@ -290,6 +290,31 @@ get_target_remotes() { # ── Disk info ──────────────────────────────────────────────── +# Compact one-line disk info: "USED/TOTAL (FREE free)" +remote_disk_info_short() { + local base="${REMOTE_BASE:-/}" + local df_out="" + case "${REMOTE_TYPE:-ssh}" in + ssh) + df_out=$(remote_exec "df -h '$base' 2>/dev/null | tail -1") || return 1 + ;; + local) + df_out=$(df -h "$base" 2>/dev/null | tail -1) || return 1 + ;; + *) + echo "N/A" + return 0 + ;; + esac + # df output: Filesystem Size Used Avail Use% Mount + local size used avail pct + size=$(echo "$df_out" | awk '{print $2}') + used=$(echo "$df_out" | awk '{print $3}') + avail=$(echo "$df_out" | awk '{print $4}') + pct=$(echo "$df_out" | awk '{print $5}') + echo "${used}/${size} (${avail} free) ${pct}" +} + remote_disk_info() { local base="${REMOTE_BASE:-/}" case "${REMOTE_TYPE:-ssh}" in diff --git a/tui/screens/remotes.py b/tui/screens/remotes.py index aaa4145..a56b24a 100644 --- a/tui/screens/remotes.py +++ b/tui/screens/remotes.py @@ -34,7 +34,8 @@ class RemotesScreen(Screen): def _refresh_table(self) -> None: table = self.query_one("#remotes-table", DataTable) table.clear(columns=True) - table.add_columns("Name", "Type", "Host/Path") + cols = table.add_columns("Name", "Type", "Host/Path", "Disk") + self._disk_col_key = cols[3] remotes = list_conf_dir("remotes.d") for name in remotes: data = parse_conf(CONFIG_DIR / "remotes.d" / f"{name}.conf") @@ -47,7 +48,8 @@ class RemotesScreen(Screen): loc = f"s3://{data.get('S3_BUCKET', '')}{data.get('REMOTE_BASE', '')}" else: loc = data.get("REMOTE_BASE", "") - table.add_row(name, rtype, loc, key=name) + table.add_row(name, rtype, loc, "loading...", key=name) + self._fetch_disk_info() def _selected_remote(self) -> str | None: table = self.query_one("#remotes-table", DataTable) @@ -95,6 +97,18 @@ class RemotesScreen(Screen): else: self.notify("Select a remote first", severity="warning") + @work + async def _fetch_disk_info(self) -> None: + remotes = list_conf_dir("remotes.d") + for name in remotes: + rc, stdout, stderr = await run_cli("remotes", "disk-info-short", f"--name={name}") + disk_text = stdout.strip() if rc == 0 and stdout.strip() else "N/A" + try: + table = self.query_one("#remotes-table", DataTable) + table.update_cell(name, self._disk_col_key, disk_text, update_width=True) + except Exception: + pass + @work async def _test_remote(self, name: str) -> None: log_screen = OperationLog(f"Testing Remote: {name}", show_spinner=False)