191 lines
6.8 KiB
Go
191 lines
6.8 KiB
Go
package services
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"clawreef/internal/models"
|
|
"clawreef/internal/repository"
|
|
)
|
|
|
|
type AgentStateReportRequest struct {
|
|
AgentID string `json:"agent_id" binding:"required"`
|
|
ReportedAt *time.Time `json:"reported_at,omitempty"`
|
|
Runtime AgentRuntimePayload `json:"runtime"`
|
|
SystemInfo map[string]interface{} `json:"system_info"`
|
|
Health map[string]interface{} `json:"health"`
|
|
}
|
|
|
|
type AgentRuntimePayload struct {
|
|
OpenClawStatus string `json:"openclaw_status"`
|
|
OpenClawPID *int `json:"openclaw_pid,omitempty"`
|
|
OpenClawVersion string `json:"openclaw_version"`
|
|
CurrentConfigRevisionID *int `json:"current_config_revision_id,omitempty"`
|
|
}
|
|
|
|
type InstanceRuntimeStatusPayload struct {
|
|
InstanceID int `json:"instance_id"`
|
|
InfraStatus string `json:"infra_status"`
|
|
AgentStatus string `json:"agent_status"`
|
|
OpenClawStatus string `json:"openclaw_status"`
|
|
OpenClawPID *int `json:"openclaw_pid,omitempty"`
|
|
OpenClawVersion *string `json:"openclaw_version,omitempty"`
|
|
CurrentConfigRevisionID *int `json:"current_config_revision_id,omitempty"`
|
|
DesiredConfigRevisionID *int `json:"desired_config_revision_id,omitempty"`
|
|
SystemInfo map[string]interface{} `json:"system_info,omitempty"`
|
|
Health map[string]interface{} `json:"health,omitempty"`
|
|
Summary map[string]interface{} `json:"summary,omitempty"`
|
|
LastReportedAt *time.Time `json:"last_reported_at,omitempty"`
|
|
}
|
|
|
|
type InstanceRuntimeStatusService interface {
|
|
Report(session *AgentSession, req AgentStateReportRequest, clientIP string) error
|
|
GetByInstanceID(instanceID int) (*InstanceRuntimeStatusPayload, error)
|
|
UpsertInfraStatus(instanceID int, infraStatus string) error
|
|
}
|
|
|
|
type instanceRuntimeStatusService struct {
|
|
runtimeRepo repository.InstanceRuntimeStatusRepository
|
|
agentRepo repository.InstanceAgentRepository
|
|
desiredStateRepo repository.InstanceDesiredStateRepository
|
|
}
|
|
|
|
func NewInstanceRuntimeStatusService(runtimeRepo repository.InstanceRuntimeStatusRepository, agentRepo repository.InstanceAgentRepository, desiredStateRepo repository.InstanceDesiredStateRepository) InstanceRuntimeStatusService {
|
|
return &instanceRuntimeStatusService{
|
|
runtimeRepo: runtimeRepo,
|
|
agentRepo: agentRepo,
|
|
desiredStateRepo: desiredStateRepo,
|
|
}
|
|
}
|
|
|
|
func (s *instanceRuntimeStatusService) Report(session *AgentSession, req AgentStateReportRequest, clientIP string) error {
|
|
if session == nil || session.Agent == nil || session.Instance == nil {
|
|
return fmt.Errorf("agent session is required")
|
|
}
|
|
if strings.TrimSpace(req.AgentID) != session.Agent.AgentID {
|
|
return fmt.Errorf("agent id does not match session")
|
|
}
|
|
|
|
status, err := s.getOrCreate(session.Instance.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
now := time.Now().UTC()
|
|
reportedAt := req.ReportedAt
|
|
if reportedAt == nil || reportedAt.IsZero() {
|
|
reportedAt = &now
|
|
}
|
|
|
|
status.AgentStatus = agentStatusOnline
|
|
if strings.TrimSpace(req.Runtime.OpenClawStatus) != "" {
|
|
status.OpenClawStatus = strings.TrimSpace(req.Runtime.OpenClawStatus)
|
|
}
|
|
status.OpenClawPID = req.Runtime.OpenClawPID
|
|
if strings.TrimSpace(req.Runtime.OpenClawVersion) != "" {
|
|
version := strings.TrimSpace(req.Runtime.OpenClawVersion)
|
|
status.OpenClawVersion = &version
|
|
}
|
|
status.CurrentConfigRevisionID = req.Runtime.CurrentConfigRevisionID
|
|
status.LastReportedAt = reportedAt
|
|
|
|
systemInfoJSON, err := marshalOptionalJSON(req.SystemInfo)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encode system info: %w", err)
|
|
}
|
|
healthJSON, err := marshalOptionalJSON(req.Health)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encode health info: %w", err)
|
|
}
|
|
status.SystemInfoJSON = systemInfoJSON
|
|
status.HealthJSON = healthJSON
|
|
if err := s.runtimeRepo.Update(status); err != nil {
|
|
return err
|
|
}
|
|
|
|
session.Agent.Status = agentStatusOnline
|
|
session.Agent.LastHeartbeatAt = reportedAt
|
|
session.Agent.LastReportedAt = reportedAt
|
|
session.Agent.LastSeenIP = optionalString(strings.TrimSpace(clientIP))
|
|
if err := s.agentRepo.Update(session.Agent); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *instanceRuntimeStatusService) GetByInstanceID(instanceID int) (*InstanceRuntimeStatusPayload, error) {
|
|
status, err := s.runtimeRepo.GetByInstanceID(instanceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if status == nil {
|
|
return nil, nil
|
|
}
|
|
payload := &InstanceRuntimeStatusPayload{
|
|
InstanceID: status.InstanceID,
|
|
InfraStatus: status.InfraStatus,
|
|
AgentStatus: status.AgentStatus,
|
|
OpenClawStatus: status.OpenClawStatus,
|
|
OpenClawPID: status.OpenClawPID,
|
|
OpenClawVersion: status.OpenClawVersion,
|
|
CurrentConfigRevisionID: status.CurrentConfigRevisionID,
|
|
DesiredConfigRevisionID: status.DesiredConfigRevisionID,
|
|
LastReportedAt: status.LastReportedAt,
|
|
}
|
|
if status.SystemInfoJSON != nil && strings.TrimSpace(*status.SystemInfoJSON) != "" {
|
|
if err := json.Unmarshal([]byte(*status.SystemInfoJSON), &payload.SystemInfo); err != nil {
|
|
return nil, fmt.Errorf("failed to decode system info: %w", err)
|
|
}
|
|
}
|
|
if status.HealthJSON != nil && strings.TrimSpace(*status.HealthJSON) != "" {
|
|
if err := json.Unmarshal([]byte(*status.HealthJSON), &payload.Health); err != nil {
|
|
return nil, fmt.Errorf("failed to decode health info: %w", err)
|
|
}
|
|
}
|
|
if status.SummaryJSON != nil && strings.TrimSpace(*status.SummaryJSON) != "" {
|
|
if err := json.Unmarshal([]byte(*status.SummaryJSON), &payload.Summary); err != nil {
|
|
return nil, fmt.Errorf("failed to decode runtime summary: %w", err)
|
|
}
|
|
}
|
|
return payload, nil
|
|
}
|
|
|
|
func (s *instanceRuntimeStatusService) UpsertInfraStatus(instanceID int, infraStatus string) error {
|
|
status, err := s.getOrCreate(instanceID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
status.InfraStatus = infraStatus
|
|
|
|
desiredState, err := s.desiredStateRepo.GetByInstanceID(instanceID)
|
|
if err == nil && desiredState != nil {
|
|
status.DesiredConfigRevisionID = desiredState.DesiredConfigRevisionID
|
|
}
|
|
return s.runtimeRepo.Update(status)
|
|
}
|
|
|
|
func (s *instanceRuntimeStatusService) getOrCreate(instanceID int) (*models.InstanceRuntimeStatus, error) {
|
|
status, err := s.runtimeRepo.GetByInstanceID(instanceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if status != nil {
|
|
return status, nil
|
|
}
|
|
now := time.Now().UTC()
|
|
status = &models.InstanceRuntimeStatus{
|
|
InstanceID: instanceID,
|
|
InfraStatus: "creating",
|
|
AgentStatus: agentStatusOffline,
|
|
OpenClawStatus: openClawStatusUnknown,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
if err := s.runtimeRepo.Create(status); err != nil {
|
|
return nil, err
|
|
}
|
|
return status, nil
|
|
}
|