TUI: async polling off UI thread, 5s SSH hard timeout + remote cooldown, at-most-one-fetch, ? help menu
This commit is contained in:
17
state.go
17
state.go
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user