Add Disk Info and Speed Test buttons to Remotes screen
- Disk Info: runs df -h and df -i on remote via SSH (or locally) - Speed Test: uploads/downloads 10MB test file via rsync, measures Mbps - Both available as CLI commands: gniza remotes disk-info/speed-test --name=NAME Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
12
bin/gniza
12
bin/gniza
@@ -274,8 +274,18 @@ run_cli() {
|
|||||||
validate_remote "$name"
|
validate_remote "$name"
|
||||||
echo "Remote '$name' is valid."
|
echo "Remote '$name' is valid."
|
||||||
;;
|
;;
|
||||||
|
disk-info)
|
||||||
|
[[ -z "$name" ]] && die "remotes disk-info requires --name=NAME"
|
||||||
|
load_remote "$name"
|
||||||
|
remote_disk_info
|
||||||
|
;;
|
||||||
|
speed-test)
|
||||||
|
[[ -z "$name" ]] && die "remotes speed-test requires --name=NAME"
|
||||||
|
load_remote "$name"
|
||||||
|
remote_speed_test
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
die "Unknown remotes action: $action (expected list|add|delete|show|test)"
|
die "Unknown remotes action: $action (expected list|add|delete|show|test|disk-info|speed-test)"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -287,3 +287,101 @@ get_target_remotes() {
|
|||||||
log_error "No remotes configured. Create one in $CONFIG_DIR/remotes.d/"
|
log_error "No remotes configured. Create one in $CONFIG_DIR/remotes.d/"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Disk info ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
remote_disk_info() {
|
||||||
|
local base="${REMOTE_BASE:-/}"
|
||||||
|
case "${REMOTE_TYPE:-ssh}" in
|
||||||
|
ssh)
|
||||||
|
echo "Disk usage on ${REMOTE_USER}@${REMOTE_HOST}:${base}"
|
||||||
|
echo "──────────────────────────────────────────"
|
||||||
|
remote_exec "df -h '$base' 2>/dev/null && echo '' && df -i '$base' 2>/dev/null"
|
||||||
|
;;
|
||||||
|
local)
|
||||||
|
echo "Disk usage on ${base}"
|
||||||
|
echo "──────────────────────────────────────────"
|
||||||
|
df -h "$base" 2>/dev/null
|
||||||
|
echo ""
|
||||||
|
df -i "$base" 2>/dev/null
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Disk info not supported for remote type: ${REMOTE_TYPE}"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Speed test ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
remote_speed_test() {
|
||||||
|
local test_size="10M"
|
||||||
|
local test_file="/tmp/.gniza_speedtest_$$"
|
||||||
|
local remote_file="${REMOTE_BASE:-.}/.gniza_speedtest_$$"
|
||||||
|
|
||||||
|
case "${REMOTE_TYPE:-ssh}" in
|
||||||
|
ssh)
|
||||||
|
echo "Speed test to ${REMOTE_USER}@${REMOTE_HOST}"
|
||||||
|
echo "Test file size: ${test_size}"
|
||||||
|
echo "──────────────────────────────────────────"
|
||||||
|
|
||||||
|
# Create local test file
|
||||||
|
dd if=/dev/urandom of="$test_file" bs=1M count=10 2>/dev/null
|
||||||
|
|
||||||
|
# Upload test
|
||||||
|
echo ""
|
||||||
|
echo "Upload test..."
|
||||||
|
local ssh_cmd
|
||||||
|
ssh_cmd=$(build_rsync_ssh_cmd)
|
||||||
|
local start_up end_up duration_up speed_up
|
||||||
|
start_up=$(date +%s%N)
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if rsync -e "$ssh_cmd" --progress "$test_file" "${REMOTE_USER}@${REMOTE_HOST}:${remote_file}" 2>/dev/null; then
|
||||||
|
end_up=$(date +%s%N)
|
||||||
|
duration_up=$(( (end_up - start_up) / 1000000 ))
|
||||||
|
if [[ "$duration_up" -gt 0 ]]; then
|
||||||
|
speed_up=$(( 10 * 1000 * 8 / duration_up ))
|
||||||
|
echo " Upload: ${speed_up} Mbps (${duration_up} ms)"
|
||||||
|
else
|
||||||
|
echo " Upload: too fast to measure"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Upload: FAILED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Download test
|
||||||
|
echo ""
|
||||||
|
echo "Download test..."
|
||||||
|
local dl_file="${test_file}_dl"
|
||||||
|
local start_dn end_dn duration_dn speed_dn
|
||||||
|
start_dn=$(date +%s%N)
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if rsync -e "$ssh_cmd" --progress "${REMOTE_USER}@${REMOTE_HOST}:${remote_file}" "$dl_file" 2>/dev/null; then
|
||||||
|
end_dn=$(date +%s%N)
|
||||||
|
duration_dn=$(( (end_dn - start_dn) / 1000000 ))
|
||||||
|
if [[ "$duration_dn" -gt 0 ]]; then
|
||||||
|
speed_dn=$(( 10 * 1000 * 8 / duration_dn ))
|
||||||
|
echo " Download: ${speed_dn} Mbps (${duration_dn} ms)"
|
||||||
|
else
|
||||||
|
echo " Download: too fast to measure"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Download: FAILED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -f "$test_file" "$dl_file" 2>/dev/null
|
||||||
|
remote_exec "rm -f '$remote_file'" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Done."
|
||||||
|
;;
|
||||||
|
local)
|
||||||
|
echo "Speed test not applicable for local remotes."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Speed test not supported for remote type: ${REMOTE_TYPE}"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ class RemotesScreen(Screen):
|
|||||||
yield Button("Add", variant="primary", id="btn-add")
|
yield Button("Add", variant="primary", id="btn-add")
|
||||||
yield Button("Edit", id="btn-edit")
|
yield Button("Edit", id="btn-edit")
|
||||||
yield Button("Test", variant="warning", id="btn-test")
|
yield Button("Test", variant="warning", id="btn-test")
|
||||||
|
yield Button("Disk Info", id="btn-disk")
|
||||||
|
yield Button("Speed Test", id="btn-speed")
|
||||||
yield Button("Delete", variant="error", id="btn-delete")
|
yield Button("Delete", variant="error", id="btn-delete")
|
||||||
yield Button("Back", id="btn-back")
|
yield Button("Back", id="btn-back")
|
||||||
yield Footer()
|
yield Footer()
|
||||||
@@ -71,6 +73,18 @@ class RemotesScreen(Screen):
|
|||||||
self._test_remote(name)
|
self._test_remote(name)
|
||||||
else:
|
else:
|
||||||
self.notify("Select a remote first", severity="warning")
|
self.notify("Select a remote first", severity="warning")
|
||||||
|
elif event.button.id == "btn-disk":
|
||||||
|
name = self._selected_remote()
|
||||||
|
if name:
|
||||||
|
self._disk_info(name)
|
||||||
|
else:
|
||||||
|
self.notify("Select a remote first", severity="warning")
|
||||||
|
elif event.button.id == "btn-speed":
|
||||||
|
name = self._selected_remote()
|
||||||
|
if name:
|
||||||
|
self._speed_test(name)
|
||||||
|
else:
|
||||||
|
self.notify("Select a remote first", severity="warning")
|
||||||
elif event.button.id == "btn-delete":
|
elif event.button.id == "btn-delete":
|
||||||
name = self._selected_remote()
|
name = self._selected_remote()
|
||||||
if name:
|
if name:
|
||||||
@@ -96,6 +110,32 @@ class RemotesScreen(Screen):
|
|||||||
log_screen.write(f"\n[red]Connection test failed (exit code {rc}).[/red]")
|
log_screen.write(f"\n[red]Connection test failed (exit code {rc}).[/red]")
|
||||||
log_screen.finish()
|
log_screen.finish()
|
||||||
|
|
||||||
|
@work
|
||||||
|
async def _disk_info(self, name: str) -> None:
|
||||||
|
log_screen = OperationLog(f"Disk Info: {name}", show_spinner=False)
|
||||||
|
self.app.push_screen(log_screen)
|
||||||
|
rc, stdout, stderr = await run_cli("remotes", "disk-info", f"--name={name}")
|
||||||
|
if stdout:
|
||||||
|
log_screen.write(stdout)
|
||||||
|
if stderr:
|
||||||
|
log_screen.write(stderr)
|
||||||
|
if rc != 0:
|
||||||
|
log_screen.write(f"\n[red]Failed to get disk info (exit code {rc}).[/red]")
|
||||||
|
log_screen.finish()
|
||||||
|
|
||||||
|
@work
|
||||||
|
async def _speed_test(self, name: str) -> None:
|
||||||
|
log_screen = OperationLog(f"Speed Test: {name}")
|
||||||
|
self.app.push_screen(log_screen)
|
||||||
|
rc, stdout, stderr = await run_cli("remotes", "speed-test", f"--name={name}")
|
||||||
|
if stdout:
|
||||||
|
log_screen.write(stdout)
|
||||||
|
if stderr:
|
||||||
|
log_screen.write(stderr)
|
||||||
|
if rc != 0:
|
||||||
|
log_screen.write(f"\n[red]Speed test failed (exit code {rc}).[/red]")
|
||||||
|
log_screen.finish()
|
||||||
|
|
||||||
def _delete_remote(self, name: str) -> None:
|
def _delete_remote(self, name: str) -> None:
|
||||||
conf = CONFIG_DIR / "remotes.d" / f"{name}.conf"
|
conf = CONFIG_DIR / "remotes.d" / f"{name}.conf"
|
||||||
if conf.is_file():
|
if conf.is_file():
|
||||||
|
|||||||
Reference in New Issue
Block a user