feat: redesign collection UI + add StopHostAfterCollect + TCP ping pre-probe
- Single "Подключиться" button flow: probe first, then show collect options - Power management checkboxes: power on before / stop after collect - Modal confirmation when enabling shutdown on already-powered-on host - StopHostAfterCollect flag: host shuts down only when explicitly requested - TCP ping (10 attempts, min 3 successes) before Redfish probe - Debug payloads checkbox (Oem/Ami/Inventory/Crc, off by default) - Remove platform_config BIOS settings collection (unreliable on AMI) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,9 @@ type CollectRequest struct {
|
||||
Password string `json:"password,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
TLSMode string `json:"tls_mode"`
|
||||
PowerOnIfHostOff bool `json:"power_on_if_host_off,omitempty"`
|
||||
PowerOnIfHostOff bool `json:"power_on_if_host_off,omitempty"`
|
||||
StopHostAfterCollect bool `json:"stop_host_after_collect,omitempty"`
|
||||
DebugPayloads bool `json:"debug_payloads,omitempty"`
|
||||
}
|
||||
|
||||
type CollectProbeResponse struct {
|
||||
|
||||
@@ -10,8 +10,10 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
@@ -1574,6 +1576,32 @@ func (s *Server) handleCollectStart(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(job.toJobResponse("Collection job accepted"))
|
||||
}
|
||||
|
||||
// pingHost dials host:port up to total times with 2s timeout each, returns true if
|
||||
// at least need attempts succeeded.
|
||||
func pingHost(host string, port int, total, need int) (bool, string) {
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
var successes atomic.Int32
|
||||
done := make(chan struct{}, total)
|
||||
for i := 0; i < total; i++ {
|
||||
go func() {
|
||||
defer func() { done <- struct{}{} }()
|
||||
conn, err := net.DialTimeout("tcp", addr, 2*time.Second)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
successes.Add(1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
for i := 0; i < total; i++ {
|
||||
<-done
|
||||
}
|
||||
n := int(successes.Load())
|
||||
if n < need {
|
||||
return false, fmt.Sprintf("Хост недоступен: только %d из %d попыток подключения к %s прошли успешно (требуется минимум %d)", n, total, addr, need)
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (s *Server) handleCollectProbe(w http.ResponseWriter, r *http.Request) {
|
||||
var req CollectRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -1595,6 +1623,11 @@ func (s *Server) handleCollectProbe(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if ok, msg := pingHost(req.Host, req.Port, 10, 3); !ok {
|
||||
jsonError(w, msg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -1967,7 +2000,9 @@ func toCollectorRequest(req CollectRequest) collector.Request {
|
||||
Password: req.Password,
|
||||
Token: req.Token,
|
||||
TLSMode: req.TLSMode,
|
||||
PowerOnIfHostOff: req.PowerOnIfHostOff,
|
||||
PowerOnIfHostOff: req.PowerOnIfHostOff,
|
||||
StopHostAfterCollect: req.StopHostAfterCollect,
|
||||
DebugPayloads: req.DebugPayloads,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user