96 lines
3.0 KiB
Go
96 lines
3.0 KiB
Go
package service
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const progressLogPrefix = "METIS_PROGRESS "
|
|
|
|
// RemoteProgressUpdate is emitted by remote workers so the UI can show
|
|
// concrete stage transitions instead of relying only on elapsed-time guesses.
|
|
type RemoteProgressUpdate struct {
|
|
Stage string `json:"stage,omitempty"`
|
|
ProgressPct float64 `json:"progress_pct,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
WrittenBytes int64 `json:"written_bytes,omitempty"`
|
|
TotalBytes int64 `json:"total_bytes,omitempty"`
|
|
}
|
|
|
|
// RemoteFlashResult captures the verified outcome of a remote flash worker run.
|
|
type RemoteFlashResult struct {
|
|
Node string `json:"node,omitempty"`
|
|
Device string `json:"device,omitempty"`
|
|
DestPath string `json:"dest_path,omitempty"`
|
|
SizeBytes int64 `json:"size_bytes,omitempty"`
|
|
Verified bool `json:"verified,omitempty"`
|
|
VerificationKind string `json:"verification_kind,omitempty"`
|
|
VerificationSummary string `json:"verification_summary,omitempty"`
|
|
BootPartition string `json:"boot_partition,omitempty"`
|
|
RootPartition string `json:"root_partition,omitempty"`
|
|
BootLabel string `json:"boot_label,omitempty"`
|
|
RootLabel string `json:"root_label,omitempty"`
|
|
BootFSType string `json:"boot_fstype,omitempty"`
|
|
RootFSType string `json:"root_fstype,omitempty"`
|
|
CheckedFiles []string `json:"checked_files,omitempty"`
|
|
}
|
|
|
|
// ProgressLogLine formats a progress update for remote worker stdout.
|
|
func ProgressLogLine(update RemoteProgressUpdate) string {
|
|
data, err := json.Marshal(update)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return progressLogPrefix + string(data)
|
|
}
|
|
|
|
func parseRemoteProgressLogs(logs string) (RemoteProgressUpdate, bool) {
|
|
scanner := bufio.NewScanner(strings.NewReader(logs))
|
|
scanner.Buffer(make([]byte, 0, 4096), 1<<20)
|
|
var latest RemoteProgressUpdate
|
|
found := false
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if !strings.HasPrefix(line, progressLogPrefix) {
|
|
continue
|
|
}
|
|
raw := strings.TrimSpace(strings.TrimPrefix(line, progressLogPrefix))
|
|
var update RemoteProgressUpdate
|
|
if err := json.Unmarshal([]byte(raw), &update); err != nil {
|
|
continue
|
|
}
|
|
latest = update
|
|
found = true
|
|
}
|
|
return latest, found
|
|
}
|
|
|
|
func (a *App) applyRemoteProgress(jobID string, update RemoteProgressUpdate) {
|
|
if strings.TrimSpace(jobID) == "" {
|
|
return
|
|
}
|
|
a.setJob(jobID, func(j *Job) {
|
|
if j == nil || j.Status != JobRunning {
|
|
return
|
|
}
|
|
if stage := strings.TrimSpace(update.Stage); stage != "" && stage != j.Stage {
|
|
j.Stage = stage
|
|
j.StageStartedAt = time.Now().UTC()
|
|
}
|
|
if update.ProgressPct > j.ProgressPct {
|
|
j.ProgressPct = update.ProgressPct
|
|
}
|
|
if message := strings.TrimSpace(update.Message); message != "" {
|
|
j.Message = message
|
|
}
|
|
if update.WrittenBytes > 0 {
|
|
j.Written = update.WrittenBytes
|
|
}
|
|
if update.TotalBytes > 0 {
|
|
j.Total = update.TotalBytes
|
|
}
|
|
})
|
|
}
|