Files
wehub-resource-sync f99010fae1
Desktop Artifacts / Desktop Build (Linux) (push) Waiting to run
Desktop Artifacts / Desktop Build (Windows) (push) Waiting to run
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Waiting to run
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

154 lines
3.8 KiB
Go

package ssh
import (
"context"
"fmt"
"os"
"time"
"go.kenn.io/agentsview/internal/db"
"go.kenn.io/agentsview/internal/remotesync"
"go.kenn.io/agentsview/internal/sync"
)
// SyncStats summarizes the outcome of a remote sync run.
type SyncStats = remotesync.SyncStats
// RemoteSync orchestrates pulling session data from a remote
// host over SSH, parsing it, and writing it to the local DB.
type RemoteSync struct {
Host string
User string
Port int
Full bool
DB *db.DB
SSHOpts []string // extra args passed to ssh (e.g. -i keyfile)
BlockedResultCategories []string
Progress sync.ProgressFunc
}
// Run executes the full remote sync flow: resolve dirs,
// download via tar, then delegate to sync.Engine for
// discovery, parsing, and writing.
func (rs *RemoteSync) Run(
ctx context.Context,
) (SyncStats, error) {
var stats SyncStats
rs.reportProgress("Resolving agent directories on " + rs.Host)
fmt.Printf(
"Resolving agent directories on %s...\n", rs.Host,
)
dirs, files, extraFiles, err := resolveDirs(
ctx, rs.Host, rs.User, rs.Port, rs.SSHOpts,
)
if err != nil {
return stats, fmt.Errorf(
"resolve dirs on %s: %w", rs.Host, err,
)
}
if len(dirs) == 0 {
rs.reportProgress("No agent directories found on " + rs.Host)
fmt.Printf("No agent directories found on %s\n", rs.Host)
return stats, nil
}
rs.reportProgress(fmt.Sprintf(
"Downloading session data from %s (%d agents)",
rs.Host, len(dirs),
))
fmt.Printf(
"Downloading session data from %s (%d agents)...\n",
rs.Host, len(dirs),
)
tmpDir, err := downloadAndExtract(
ctx, rs.Host, rs.User, rs.Port, rs.SSHOpts, dirs, files, extraFiles,
)
if err != nil {
return stats, fmt.Errorf(
"download from %s: %w", rs.Host, err,
)
}
defer os.RemoveAll(tmpDir)
rs.reportProgress("Download complete")
fmt.Printf("Download complete.\n")
t0 := time.Now()
lastPrint := t0
var lastProgress sync.Progress
progress := func(p sync.Progress) {
if p.Detail == "" {
p.Detail = "Processing sessions from " + rs.Host
}
if rs.Progress != nil {
rs.Progress(p)
}
lastProgress = p
now := time.Now()
if now.Sub(lastPrint) < 500*time.Millisecond {
return
}
lastPrint = now
elapsed := now.Sub(t0).Truncate(time.Millisecond)
fmt.Printf(
"\r %d/%d sessions (%s)...",
p.SessionsDone, p.SessionsTotal, elapsed,
)
}
fmt.Printf("Processing sessions...\n")
stats, err = remotesync.Importer{
Host: rs.Host,
Full: rs.Full,
DB: rs.DB,
BlockedResultCategories: rs.BlockedResultCategories,
Progress: progress,
}.ImportExtracted(ctx, remotesync.TargetSet{
Dirs: dirs,
Files: files,
ExtraFiles: extraFiles,
}, tmpDir)
if lastProgress.SessionsTotal > 0 {
elapsed := time.Since(t0).Truncate(time.Millisecond)
fmt.Printf(
"\r %d/%d sessions (%s) \n",
lastProgress.SessionsDone,
lastProgress.SessionsTotal, elapsed,
)
}
if err != nil {
return stats, err
}
fmt.Printf(
"Synced %d sessions from %s",
stats.SessionsSynced, rs.Host,
)
if stats.Skipped > 0 {
fmt.Printf(" (%d unchanged)", stats.Skipped)
}
if stats.Failed > 0 {
fmt.Printf(" (%d failed)", stats.Failed)
}
fmt.Println()
rs.reportProgress(remoteSyncSummary(rs.Host, stats))
return stats, nil
}
func (rs *RemoteSync) reportProgress(detail string) {
if rs.Progress == nil {
return
}
rs.Progress(sync.Progress{Detail: detail})
}
func remoteSyncSummary(host string, stats SyncStats) string {
summary := fmt.Sprintf("Synced %d sessions from %s", stats.SessionsSynced, host)
if stats.Skipped > 0 {
summary += fmt.Sprintf(" (%d unchanged)", stats.Skipped)
}
if stats.Failed > 0 {
summary += fmt.Sprintf(" (%d failed)", stats.Failed)
}
return summary
}