diff --git a/internal/service/config_assets.go b/internal/service/config_assets.go index 9d85e38..34d4096 100644 --- a/internal/service/config_assets.go +++ b/internal/service/config_assets.go @@ -101,9 +101,8 @@ type TokenServiceConfig struct { } type AlarmServiceConfig struct { + GetTokenURL string `json:"get_token_url"` PutMessageURL string `json:"put_message_url"` - Username string `json:"username"` - Password string `json:"password"` TenantCode string `json:"tenant_code"` } @@ -1375,32 +1374,24 @@ func (s *ConfigPreviewService) SaveIntegrationServiceAsset(asset ConfigIntegrati return fmt.Errorf("object storage %s is required", key) } } - case "token_service": - if asset.TokenService == nil { - return fmt.Errorf("token service config is required") - } - setAnyString(configMap, "get_token_url", asset.TokenService.GetTokenURL) - setAnyString(configMap, "username", asset.TokenService.Username) - setAnyString(configMap, "password", asset.TokenService.Password) - setAnyString(configMap, "tenant_code", asset.TokenService.TenantCode) - if strings.TrimSpace(stringValue(configMap["get_token_url"])) == "" { - return fmt.Errorf("token service get_token_url is required") - } case "alarm_service": if asset.AlarmService == nil { return fmt.Errorf("alarm service config is required") } + setAnyString(configMap, "get_token_url", asset.AlarmService.GetTokenURL) setAnyString(configMap, "put_message_url", asset.AlarmService.PutMessageURL) - setAnyString(configMap, "username", asset.AlarmService.Username) - setAnyString(configMap, "password", asset.AlarmService.Password) setAnyString(configMap, "tenant_code", asset.AlarmService.TenantCode) - if strings.TrimSpace(stringValue(configMap["put_message_url"])) == "" { - return fmt.Errorf("alarm service put_message_url is required") + if strings.TrimSpace(stringValue(configMap["get_token_url"])) == "" && strings.TrimSpace(stringValue(configMap["put_message_url"])) == "" { + return fmt.Errorf("alarm service requires at least one of get_token_url or put_message_url") } + case "custom": + raw["config"] = asset.Raw default: return fmt.Errorf("unsupported third-party service type: %s", serviceType) } - raw["config"] = configMap + if serviceType != "custom" { + raw["config"] = configMap + } body, err := marshalConfigJSON(raw) if err != nil { return err @@ -1659,22 +1650,19 @@ func integrationServiceAssetFromRecord(record storage.IntegrationServiceRecord) SecretKey: firstString(sourceMap["secret_key"], stringValue(sourceMap["minio_secret_key"])), } item.AddressSummary = strings.TrimSpace(strings.Trim(strings.Join([]string{item.ObjectStorage.Endpoint, item.ObjectStorage.Bucket}, " / "), " /")) - case "token_service": - item.TokenService = &TokenServiceConfig{ - GetTokenURL: stringValue(sourceMap["get_token_url"]), - Username: stringValue(sourceMap["username"]), - Password: stringValue(sourceMap["password"]), - TenantCode: stringValue(sourceMap["tenant_code"]), - } - item.AddressSummary = item.TokenService.GetTokenURL case "alarm_service": item.AlarmService = &AlarmServiceConfig{ + GetTokenURL: stringValue(sourceMap["get_token_url"]), PutMessageURL: stringValue(sourceMap["put_message_url"]), - Username: stringValue(sourceMap["username"]), - Password: stringValue(sourceMap["password"]), TenantCode: stringValue(sourceMap["tenant_code"]), } - item.AddressSummary = item.AlarmService.PutMessageURL + summary := strings.TrimSpace(item.AlarmService.PutMessageURL) + if summary == "" { + summary = item.AlarmService.GetTokenURL + } + item.AddressSummary = summary + case "custom": + item.AddressSummary = "自定义" } return item, nil } @@ -1719,10 +1707,10 @@ func integrationTypeLabel(v string) string { switch strings.TrimSpace(v) { case "object_storage": return "对象存储" - case "token_service": - return "认证服务" case "alarm_service": return "告警服务" + case "custom": + return "自定义" default: return strings.TrimSpace(v) } diff --git a/internal/service/config_preview.go b/internal/service/config_preview.go index 19f797f..5703732 100644 --- a/internal/service/config_preview.go +++ b/internal/service/config_preview.go @@ -359,7 +359,7 @@ func (s *ConfigPreviewService) resolveSceneBindings(raw map[string]any) (map[str expected string }{ {slot: "object_storage_main", expected: "object_storage"}, - {slot: "token_service_main", expected: "token_service"}, + {slot: "token_service_main", expected: "alarm_service"}, {slot: "alarm_service_main", expected: "alarm_service"}, } { serviceRef := bindingField(serviceBindings, binding.slot, "service_ref") @@ -405,26 +405,22 @@ func resolvedServiceBinding(asset *ConfigIntegrationServiceAsset) map[string]any "access_key": asset.ObjectStorage.AccessKey, "secret_key": asset.ObjectStorage.SecretKey, } - case "token_service": - if asset.TokenService == nil { - return nil - } - return map[string]any{ - "get_token_url": asset.TokenService.GetTokenURL, - "username": asset.TokenService.Username, - "password": asset.TokenService.Password, - "tenant_code": asset.TokenService.TenantCode, - } case "alarm_service": if asset.AlarmService == nil { return nil } return map[string]any{ + "get_token_url": asset.AlarmService.GetTokenURL, "put_message_url": asset.AlarmService.PutMessageURL, - "username": asset.AlarmService.Username, - "password": asset.AlarmService.Password, "tenant_code": asset.AlarmService.TenantCode, } + case "custom": + if cfg, ok := asset.Raw["config"]; ok { + if m, ok := cfg.(map[string]any); ok { + return m + } + } + return nil default: return nil } diff --git a/internal/service/config_preview_test.go b/internal/service/config_preview_test.go index 2fb396c..701de69 100644 --- a/internal/service/config_preview_test.go +++ b/internal/service/config_preview_test.go @@ -186,7 +186,7 @@ func TestConfigPreviewServiceRenderProfileEditorWritesResolvedBindings(t *testin t.Fatalf("SaveVideoSource: %v", err) } saveIntegrationServiceForPreviewTest(t, repo, "minio_main", "object_storage", `{"name":"minio_main","type":"object_storage","config":{"endpoint":"http://10.0.0.49:9000","bucket":"myminio","access_key":"admin","secret_key":"password"}}`) - saveIntegrationServiceForPreviewTest(t, repo, "token_main", "token_service", `{"name":"token_main","type":"token_service","config":{"get_token_url":"http://10.0.0.49:8080/api/getToken","tenant_code":"32"}}`) + saveIntegrationServiceForPreviewTest(t, repo, "token_main", "alarm_service", `{"name":"token_main","type":"alarm_service","config":{"get_token_url":"http://10.0.0.49:8080/api/getToken","tenant_code":"32"}}`) saveIntegrationServiceForPreviewTest(t, repo, "alarm_main", "alarm_service", `{"name":"alarm_main","type":"alarm_service","config":{"put_message_url":"http://10.0.0.49:8080/api/putMessage","tenant_code":"32"}}`) svc := NewConfigPreviewService(&config.Config{MediaRepoPath: root}, repo) @@ -248,7 +248,7 @@ func TestConfigPreviewServiceRenderProfileEditorAllowsUnboundOptionalServiceSlot "source":"standard", "slots":{ "inputs":[{"name":"video_input_main","type":"video_source","required":true,"description":"主视频输入"}], - "services":[{"name":"token_service_main","type":"token_service","required":false,"description":"认证服务"}], + "services":[{"name":"token_service_main","type":"alarm_service","required":false,"description":"告警服务"}], "outputs":[{"name":"stream_output_main","type":"stream_publish","required":true,"description":"主视频输出"}] }, "template":{ diff --git a/internal/web/ui.go b/internal/web/ui.go index 85843cc..87d1062 100644 --- a/internal/web/ui.go +++ b/internal/web/ui.go @@ -131,6 +131,7 @@ type PageData struct { TemplateCloneDesc string TemplateCreateMode string OverlayDraftJSON string + IntegrationConfigDraft string AuditEntries []storage.AuditLogRecord PersistedConfig *storage.DeviceConfigStateRecord DBPath string @@ -419,8 +420,6 @@ func NewUI(discovery *service.DiscoveryService, registry *service.RegistryServic return "视频源" case "object_storage": return "对象存储" - case "token_service": - return "认证服务" case "alarm_service": return "告警服务" case "stream_publish": @@ -2468,6 +2467,16 @@ func (u *UI) pageAssetIntegrations(w http.ResponseWriter, r *http.Request) { } else { data.AssetIntegrationEditing = newMode || editMode } + if data.AssetIntegration != nil && data.AssetIntegration.Type == "custom" { + if configRaw, ok := data.AssetIntegration.Raw["config"]; ok { + if s, err := compactJSON(configRaw); err == nil { + data.IntegrationConfigDraft = s + } + } + if data.IntegrationConfigDraft == "" { + data.IntegrationConfigDraft = "{}" + } + } u.render(w, r, "assets", data) } @@ -2478,29 +2487,34 @@ func (u *UI) actionAssetIntegrationSave(w http.ResponseWriter, r *http.Request) } _ = r.ParseForm() enabled := strings.TrimSpace(r.FormValue("enabled")) == "1" || strings.EqualFold(strings.TrimSpace(r.FormValue("enabled")), "true") || strings.EqualFold(strings.TrimSpace(r.FormValue("enabled")), "on") + serviceType := strings.TrimSpace(r.FormValue("type")) asset := service.ConfigIntegrationServiceAsset{ Name: strings.TrimSpace(r.FormValue("name")), - Type: strings.TrimSpace(r.FormValue("type")), + Type: serviceType, Description: strings.TrimSpace(r.FormValue("description")), Enabled: enabled, - ObjectStorage: &service.ObjectStorageConfig{ - Endpoint: strings.TrimSpace(r.FormValue("endpoint")), - Bucket: strings.TrimSpace(r.FormValue("bucket")), - AccessKey: strings.TrimSpace(r.FormValue("access_key")), - SecretKey: strings.TrimSpace(r.FormValue("secret_key")), - }, - TokenService: &service.TokenServiceConfig{ - GetTokenURL: strings.TrimSpace(r.FormValue("get_token_url")), - Username: strings.TrimSpace(r.FormValue("username")), - Password: strings.TrimSpace(r.FormValue("password")), - TenantCode: strings.TrimSpace(r.FormValue("tenant_code")), - }, - AlarmService: &service.AlarmServiceConfig{ - PutMessageURL: strings.TrimSpace(r.FormValue("put_message_url")), - Username: strings.TrimSpace(r.FormValue("alarm_username")), - Password: strings.TrimSpace(r.FormValue("alarm_password")), - TenantCode: strings.TrimSpace(r.FormValue("alarm_tenant_code")), - }, + ObjectStorage: &service.ObjectStorageConfig{}, + AlarmService: &service.AlarmServiceConfig{}, + } + if serviceType == "custom" { + configRaw := strings.TrimSpace(r.FormValue("config_json")) + if configRaw == "" { + configRaw = "{}" + } + var raw map[string]any + if err := json.Unmarshal([]byte(configRaw), &raw); err != nil { + http.Redirect(w, r, "/ui/assets/integrations?error="+urlQueryEscape("自定义配置 JSON 格式不正确:"+err.Error())+"&name="+url.PathEscape(asset.Name), http.StatusFound) + return + } + asset.Raw = raw + } else { + asset.ObjectStorage.Endpoint = strings.TrimSpace(r.FormValue("endpoint")) + asset.ObjectStorage.Bucket = strings.TrimSpace(r.FormValue("bucket")) + asset.ObjectStorage.AccessKey = strings.TrimSpace(r.FormValue("access_key")) + asset.ObjectStorage.SecretKey = strings.TrimSpace(r.FormValue("secret_key")) + asset.AlarmService.GetTokenURL = strings.TrimSpace(r.FormValue("get_token_url")) + asset.AlarmService.PutMessageURL = strings.TrimSpace(r.FormValue("put_message_url")) + asset.AlarmService.TenantCode = strings.TrimSpace(r.FormValue("tenant_code")) } if err := u.preview.SaveIntegrationServiceAsset(asset); err != nil { http.Redirect(w, r, "/ui/assets/integrations?error="+urlQueryEscape(err.Error())+"&name="+url.PathEscape(asset.Name), http.StatusFound) diff --git a/internal/web/ui/templates/assets.html b/internal/web/ui/templates/assets.html index 3811e92..2055565 100644 --- a/internal/web/ui/templates/assets.html +++ b/internal/web/ui/templates/assets.html @@ -98,35 +98,40 @@ -
{{json .AssetIntegration.Raw}}
+