3588AdminBackend/internal/service/config_assets.go

355 lines
10 KiB
Go

package service
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"3588AdminBackend/internal/storage"
)
type ConfigTemplateAsset struct {
Name string `json:"name"`
Path string `json:"path"`
Description string `json:"description"`
Source string `json:"source"`
NodeCount int `json:"node_count"`
EdgeCount int `json:"edge_count"`
MinIOEndpoint string `json:"minio_endpoint"`
MinIOBucket string `json:"minio_bucket"`
ExternalGetTokenURL string `json:"external_get_token_url"`
ExternalPutMessageURL string `json:"external_put_message_url"`
TenantCode string `json:"tenant_code"`
AdvancedParams map[string]any `json:"advanced_params"`
Raw map[string]any `json:"raw"`
}
type ConfigProfileAsset struct {
Name string `json:"name"`
Path string `json:"path"`
Description string `json:"description"`
BusinessName string `json:"business_name"`
QueueSize int `json:"queue_size"`
QueueStrategy string `json:"queue_strategy"`
Instances []ConfigProfileInstanceAsset `json:"instances"`
Raw map[string]any `json:"raw"`
}
type ConfigProfileInstanceAsset struct {
Name string `json:"name"`
Template string `json:"template"`
DisplayName string `json:"display_name"`
DeviceCode string `json:"device_code"`
SiteName string `json:"site_name"`
RTSPURL string `json:"rtsp_url"`
PublishHLSPath string `json:"publish_hls_path"`
PublishRTSPPort string `json:"publish_rtsp_port"`
PublishRTSPPath string `json:"publish_rtsp_path"`
ChannelNo string `json:"channel_no"`
AdvancedParams map[string]any `json:"advanced_params"`
}
type ConfigOverlayAsset struct {
Name string `json:"name"`
Path string `json:"path"`
Description string `json:"description"`
OverrideTargets []string `json:"override_targets"`
OverrideTargetNum int `json:"override_target_num"`
Raw map[string]any `json:"raw"`
}
func (s *ConfigPreviewService) ListTemplateAssets() ([]ConfigTemplateAsset, error) {
sources, err := s.ListSources()
if err != nil {
return nil, err
}
items := make([]ConfigTemplateAsset, 0, len(sources.Templates))
for _, source := range sources.Templates {
item, err := s.GetTemplateAsset(source.Name)
if err != nil {
continue
}
items = append(items, *item)
}
return items, nil
}
func (s *ConfigPreviewService) GetTemplateAsset(name string) (*ConfigTemplateAsset, error) {
raw, path, err := s.readAssetJSON("templates", name)
if err != nil {
return nil, err
}
templateMap, _ := raw["template"].(map[string]any)
paramsMap, _ := raw["params"].(map[string]any)
nodes, _ := templateMap["nodes"].([]any)
edges, _ := templateMap["edges"].([]any)
advanced := cloneMap(paramsMap)
for _, key := range []string{
"minio_endpoint",
"minio_bucket",
"external_get_token_url",
"external_put_message_url",
"tenant_code",
} {
delete(advanced, key)
}
if len(advanced) == 0 {
advanced = nil
}
return &ConfigTemplateAsset{
Name: firstString(raw["name"], name),
Path: path,
Description: stringValue(raw["description"]),
Source: stringValue(raw["source"]),
NodeCount: len(nodes),
EdgeCount: len(edges),
MinIOEndpoint: stringValue(paramsMap["minio_endpoint"]),
MinIOBucket: stringValue(paramsMap["minio_bucket"]),
ExternalGetTokenURL: stringValue(paramsMap["external_get_token_url"]),
ExternalPutMessageURL: stringValue(paramsMap["external_put_message_url"]),
TenantCode: valueString(paramsMap["tenant_code"]),
AdvancedParams: advanced,
Raw: raw,
}, nil
}
func (s *ConfigPreviewService) ListProfileAssets() ([]ConfigProfileAsset, error) {
sources, err := s.ListSources()
if err != nil {
return nil, err
}
items := make([]ConfigProfileAsset, 0, len(sources.Profiles))
for _, source := range sources.Profiles {
item, err := s.GetProfileAsset(source.Name)
if err != nil {
continue
}
items = append(items, *item)
}
return items, nil
}
func (s *ConfigPreviewService) GetProfileAsset(name string) (*ConfigProfileAsset, error) {
raw, path, err := s.readAssetJSON("profiles", name)
if err != nil {
return nil, err
}
queueMap, _ := raw["queue"].(map[string]any)
instancesRaw, _ := raw["instances"].([]any)
instances := make([]ConfigProfileInstanceAsset, 0, len(instancesRaw))
for _, item := range instancesRaw {
instanceMap, _ := item.(map[string]any)
paramsMap, _ := instanceMap["params"].(map[string]any)
advanced := cloneMap(paramsMap)
for _, key := range []string{
"display_name",
"device_code",
"site_name",
"rtsp_url",
"publish_hls_path",
"publish_rtsp_port",
"publish_rtsp_path",
"channel_no",
} {
delete(advanced, key)
}
if len(advanced) == 0 {
advanced = nil
}
instances = append(instances, ConfigProfileInstanceAsset{
Name: stringValue(instanceMap["name"]),
Template: stringValue(instanceMap["template"]),
DisplayName: stringValue(paramsMap["display_name"]),
DeviceCode: stringValue(paramsMap["device_code"]),
SiteName: stringValue(paramsMap["site_name"]),
RTSPURL: stringValue(paramsMap["rtsp_url"]),
PublishHLSPath: stringValue(paramsMap["publish_hls_path"]),
PublishRTSPPort: valueString(paramsMap["publish_rtsp_port"]),
PublishRTSPPath: stringValue(paramsMap["publish_rtsp_path"]),
ChannelNo: stringValue(paramsMap["channel_no"]),
AdvancedParams: advanced,
})
}
return &ConfigProfileAsset{
Name: firstString(raw["name"], name),
Path: path,
Description: stringValue(raw["description"]),
BusinessName: stringValue(raw["business_name"]),
QueueSize: intValue(queueMap["size"]),
QueueStrategy: stringValue(queueMap["strategy"]),
Instances: instances,
Raw: raw,
}, nil
}
func (s *ConfigPreviewService) ListOverlayAssets() ([]ConfigOverlayAsset, error) {
sources, err := s.ListSources()
if err != nil {
return nil, err
}
items := make([]ConfigOverlayAsset, 0, len(sources.Overlays))
for _, source := range sources.Overlays {
item, err := s.GetOverlayAsset(source.Name)
if err != nil {
continue
}
items = append(items, *item)
}
return items, nil
}
func (s *ConfigPreviewService) GetOverlayAsset(name string) (*ConfigOverlayAsset, error) {
raw, path, err := s.readAssetJSON("overlays", name)
if err != nil {
return nil, err
}
targets := make([]string, 0)
if overrides, ok := raw["instance_overrides"].(map[string]any); ok {
for key := range overrides {
targets = append(targets, key)
}
sort.Strings(targets)
}
return &ConfigOverlayAsset{
Name: name,
Path: path,
Description: stringValue(raw["description"]),
OverrideTargets: targets,
OverrideTargetNum: len(targets),
Raw: raw,
}, nil
}
func (s *ConfigPreviewService) readAssetJSON(kind string, name string) (map[string]any, string, error) {
if s != nil && s.assets != nil {
raw, path, ok, err := s.readRepoAssetJSON(kind, name)
if err != nil {
return nil, "", err
}
if ok {
return raw, path, nil
}
}
root := s.mediaRepoRoot()
if root == "" {
return nil, "", fmt.Errorf("media repo path is not configured")
}
if err := validateConfigName(name); err != nil {
return nil, "", err
}
path := filepath.Join(root, "configs", kind, name+".json")
body, err := os.ReadFile(path)
if err != nil {
return nil, "", err
}
var raw map[string]any
if err := json.Unmarshal(body, &raw); err != nil {
return nil, "", err
}
return raw, path, nil
}
func (s *ConfigPreviewService) readRepoAssetJSON(kind string, name string) (map[string]any, string, bool, error) {
if err := validateConfigName(name); err != nil {
return nil, "", false, err
}
var (
record *storage.AssetRecord
err error
)
switch kind {
case "templates":
record, err = s.assets.GetTemplate(name)
case "profiles":
record, err = s.assets.GetProfile(name)
case "overlays":
record, err = s.assets.GetOverlay(name)
default:
return nil, "", false, fmt.Errorf("unsupported asset kind: %s", kind)
}
if err != nil {
return nil, "", true, err
}
if record == nil {
return nil, "", false, nil
}
var raw map[string]any
if err := json.Unmarshal([]byte(record.BodyJSON), &raw); err != nil {
return nil, "", true, err
}
if raw == nil {
raw = map[string]any{}
}
if strings.TrimSpace(record.Description) != "" {
raw["description"] = record.Description
}
if kind == "profiles" {
if strings.TrimSpace(record.TemplateName) != "" {
raw["template_name"] = record.TemplateName
}
if strings.TrimSpace(record.BusinessName) != "" && stringValue(raw["business_name"]) == "" {
raw["business_name"] = record.BusinessName
}
}
return raw, repoAssetPath(kind, name), true, nil
}
func cloneMap(in map[string]any) map[string]any {
if len(in) == 0 {
return map[string]any{}
}
out := make(map[string]any, len(in))
for k, v := range in {
out[k] = v
}
return out
}
func stringValue(v any) string {
if s, ok := v.(string); ok {
return strings.TrimSpace(s)
}
return ""
}
func valueString(v any) string {
switch value := v.(type) {
case string:
return strings.TrimSpace(value)
case float64:
if float64(int(value)) == value {
return fmt.Sprintf("%d", int(value))
}
return fmt.Sprintf("%v", value)
case int:
return fmt.Sprintf("%d", value)
case int64:
return fmt.Sprintf("%d", value)
default:
return ""
}
}
func firstString(v any, fallback string) string {
if got := stringValue(v); got != "" {
return got
}
return fallback
}
func intValue(v any) int {
switch value := v.(type) {
case int:
return value
case int64:
return int(value)
case float64:
return int(value)
default:
return 0
}
}