Files
Juan Fernandez 5368b99636 Policy status page: automation activity history, reset endpoint, and details UI
Resolves #38670 

Adds the backend and frontend for the Policy status page — a historical, per-host view of policy automation outcomes — plus a way to reset a policy's results.
2026-06-19 12:13:36 -04:00

73 lines
1.8 KiB
Go

package http
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"time"
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
)
// errWithStatus is an error with a particular status code.
type errWithStatus struct {
err string
statusCode int
body string
}
// Error implements the error interface.
func (e *errWithStatus) Error() string {
return e.err
}
// StatusCode implements the StatusCoder interface for returning custom status codes.
func (e *errWithStatus) StatusCode() int {
return e.statusCode
}
// Body returns the response body of the failed request, so callers
// can surface what the remote server returned.
func (e *errWithStatus) Body() string {
return e.body
}
// PostJSONWithTimeout marshals v as JSON and POSTs it to the given URL with a 30-second timeout.
func PostJSONWithTimeout(ctx context.Context, url string, v any, logger *slog.Logger) error {
jsonBytes, err := json.Marshal(v)
if err != nil {
return err
}
client := fleethttp.NewClient(fleethttp.WithTimeout(30 * time.Second))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonBytes))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to POST to %s: %s, request-size=%d", MaskSecretURLParams(url), MaskURLError(err), len(jsonBytes))
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
bodyStr := string(body)
logger.DebugContext(ctx, "non-success response from POST",
"url", MaskSecretURLParams(url),
"status_code", resp.StatusCode,
"body", bodyStr,
)
return &errWithStatus{err: fmt.Sprintf("error posting to %s", MaskSecretURLParams(url)), statusCode: resp.StatusCode, body: bodyStr}
}
return nil
}