Files
managed-portal/internal/managed/manager.go
2026-04-27 10:04:36 +08:00

210 lines
5.4 KiB
Go

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 ""
}