package managed import ( "context" "fmt" "net/http" "strings" ) type Manager struct { registry *Registry docker DockerRuntime remote *RemoteClient } type ServiceState struct { ID string `json:"id"` DisplayName string `json:"display_name"` ProjectType string `json:"project_type"` ProjectRoot string `json:"project_root"` ContainerName string `json:"container_name"` APIBaseURL string `json:"api_base_url"` ServiceName string `json:"service_name"` ConfigPath string `json:"config_path"` RTSPField string `json:"rtsp_field"` ResultType string `json:"result_type"` ResultPaths map[string]string `json:"result_paths"` Status string `json:"status"` RTSP string `json:"rtsp,omitempty"` Summary *ResultSummary `json:"summary,omitempty"` ResultFiles []ResultFile `json:"result_files,omitempty"` ConfigError string `json:"config_error,omitempty"` ResultError string `json:"result_error,omitempty"` ServiceError string `json:"service_error,omitempty"` } func NewManager(registry *Registry, docker DockerRuntime, remote *RemoteClient) *Manager { if registry == nil { registry = EmptyRegistry() } if docker == nil { docker = NewDockerController() } if remote == nil { remote = NewRemoteClient(nil) } return &Manager{ registry: registry, docker: docker, remote: remote, } } func (m *Manager) List() []*ServiceState { states := make([]*ServiceState, 0, len(m.registry.Services)) for _, service := range m.registry.Services { states = append(states, m.snapshot(service, false)) } return states } func (m *Manager) Detail(id string) (*ServiceState, error) { service, err := m.lookup(id) if err != nil { return nil, err } return m.snapshot(service, true), nil } func (m *Manager) Summary(id string) (*ResultSummary, error) { service, err := m.lookup(id) if err != nil { return nil, err } return m.remote.GetSummary(context.Background(), service) } func (m *Manager) Files(id string) ([]ResultFile, error) { service, err := m.lookup(id) if err != nil { return nil, err } return m.remote.GetFiles(context.Background(), service) } func (m *Manager) PreviewFile(id, path string, lines int) (*FilePreview, error) { service, err := m.lookup(id) if err != nil { return nil, err } return m.remote.PreviewFile(context.Background(), service, path, lines) } func (m *Manager) Download(ctx context.Context, id, path string) (*http.Response, error) { service, err := m.lookup(id) if err != nil { return nil, err } return m.remote.Download(ctx, service, path) } func (m *Manager) UpdateRTSP(id, rtsp string) (*ServiceState, error) { service, err := m.lookup(id) if err != nil { return nil, err } if strings.TrimSpace(rtsp) == "" { return nil, fmt.Errorf("rtsp url is required") } if _, err := m.remote.UpdateRTSP(context.Background(), service, rtsp); err != nil { return nil, err } return m.snapshot(service, true), nil } func (m *Manager) Restart(id string) (*ServiceState, error) { service, err := m.lookup(id) if err != nil { return nil, err } if err := m.docker.RestartContainer(service.ContainerName); err != nil { return nil, err } return m.snapshot(service, true), nil } func (m *Manager) lookup(id string) (Service, error) { service, ok := m.registry.Get(id) if !ok { return Service{}, fmt.Errorf("%w: %s", ErrServiceNotFound, id) } return service, nil } func (m *Manager) snapshot(service Service, includeFiles bool) *ServiceState { state := &ServiceState{ ID: service.ID, DisplayName: service.DisplayName, ProjectType: service.ProjectType, ProjectRoot: service.ProjectRoot, ContainerName: service.ContainerName, APIBaseURL: service.APIBaseURL, ServiceName: service.ServiceName, ConfigPath: service.ConfigPath, RTSPField: service.RTSPField, ResultType: service.ResultType, ResultPaths: service.ResultPaths, Status: "unknown", } if payload, err := m.remote.GetConfig(context.Background(), service); err == nil { state.RTSP = extractRTSP(payload) state.ConfigPath = extractConfigPath(payload) } else { state.ConfigError = err.Error() } if status, err := m.docker.GetContainerStatus(service.ContainerName); err == nil { state.Status = status } else { state.Status = status if !IsDockerUnavailable(err) { state.ServiceError = err.Error() } } if summary, err := m.remote.GetSummary(context.Background(), service); err == nil { state.Summary = summary } else { state.ResultError = err.Error() } if includeFiles { if files, err := m.remote.GetFiles(context.Background(), service); err == nil { state.ResultFiles = files } else if state.ResultError == "" { state.ResultError = err.Error() } } return state } func extractRTSP(payload map[string]any) string { if payload == nil { return "" } if stream, ok := payload["stream"].(map[string]any); ok { if rtsp, ok := stream["rtsp_url"].(string); ok { return strings.TrimSpace(rtsp) } } if runtime, ok := payload["runtime"].(map[string]any); ok { if rtsp, ok := runtime["rtsp_url"].(string); ok { return strings.TrimSpace(rtsp) } } return "" } func extractConfigPath(payload map[string]any) string { if payload == nil { return "" } if configPath, ok := payload["config_path"].(string); ok { return strings.TrimSpace(configPath) } return "" }