TUI: async polling off UI thread, 5s SSH hard timeout + remote cooldown, at-most-one-fetch, ? help menu

This commit is contained in:
2026-08-06 15:12:17 +10:00
parent 820f4a2f8b
commit b24f9012d6
2 changed files with 115 additions and 45 deletions

View File

@@ -1,6 +1,7 @@
package main
import (
"context"
"encoding/json"
"fmt"
"os"
@@ -275,15 +276,25 @@ func readRemoteStates(cfg RemoteConfig) ([]AgentState, error) {
remotePath = filepath.Join(home, ".pi", "agent", "dashboard")
}
// SSH in, cat all JSON files
cmd := exec.Command("ssh",
"-o", "ConnectTimeout=5",
// SSH in, cat all JSON files. Hard 5s overall timeout (CommandContext)
// plus a 3s connect timeout — a slow or hung remote must never block the
// dashboard or leave ssh processes accumulating.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "ssh",
"-o", "ConnectTimeout=3",
"-o", "BatchMode=yes",
"-o", "ServerAliveInterval=5",
"-o", "ServerAliveCountMax=1",
fmt.Sprintf("%s@%s", user, hostPort),
fmt.Sprintf("cat %s/*.json 2>/dev/null || true", remotePath),
)
output, err := cmd.Output()
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
return nil, fmt.Errorf("ssh %s: timeout after 5s", cfg.Host)
}
return nil, fmt.Errorf("ssh %s: %w", cfg.Host, err)
}