diff --git a/api/dns/handler.go b/api/dns/handler.go index d2873edd..d0dcf5b5 100644 --- a/api/dns/handler.go +++ b/api/dns/handler.go @@ -241,6 +241,24 @@ func UpdateDDNSConfig(c *gin.Context) { c.JSON(http.StatusOK, toDDNSResponse(cfg)) } +// DeleteDDNSConfig removes DDNS settings for a domain and stops its schedule. +func DeleteDDNSConfig(c *gin.Context) { + domainID := cast.ToUint64(c.Param("id")) + + svc := dnsService.NewService() + if err := svc.DeleteDDNSConfig(c.Request.Context(), domainID); err != nil { + cosy.ErrHandler(c, err) + return + } + + if err := cron.RemoveDDNSJob(domainID); err != nil { + cosy.ErrHandler(c, err) + return + } + + c.Status(http.StatusNoContent) +} + func buildPagination(page, perPage int, total int64) model.Pagination { page = lo.If(page < 1, 1).Else(page) perPage = lo.If(perPage <= 0, 50).Else(perPage) diff --git a/api/dns/router.go b/api/dns/router.go index 4ab14aa7..b8c5b8cb 100644 --- a/api/dns/router.go +++ b/api/dns/router.go @@ -24,6 +24,7 @@ func InitRouter(r *gin.RouterGroup) { group.GET("/domains/:id/ddns", GetDDNSConfig) group.PUT("/domains/:id/ddns", UpdateDDNSConfig) + group.DELETE("/domains/:id/ddns", DeleteDDNSConfig) group.GET("/ddns", ListDDNSConfig) } diff --git a/api/nginx/control.go b/api/nginx/control.go index 4aaee0b3..901d6650 100644 --- a/api/nginx/control.go +++ b/api/nginx/control.go @@ -9,6 +9,17 @@ import ( "github.com/uozi-tech/cosy" ) +func buildNamespaceTestConfigResponse(namespaceID uint64, result nginx.TestConfigResult) gin.H { + return gin.H{ + "message": result.Message, + "level": result.Level, + "namespace_id": namespaceID, + "test_scope": result.TestScope, + "sandbox_status": result.SandboxStatus, + "error_category": result.ErrorCategory, + } +} + // Reload reloads the nginx func Reload(c *gin.Context) { nginx.Control(nginx.Reload).Resp(c) @@ -17,10 +28,8 @@ func Reload(c *gin.Context) { // TestConfig tests the nginx config func TestConfig(c *gin.Context) { lastResult := nginx.Control(nginx.TestConfig) - c.JSON(http.StatusOK, gin.H{ - "message": lastResult.GetOutput(), - "level": lastResult.GetLevel(), - }) + result := nginx.NewTestConfigResult(lastResult.GetStdOut(), lastResult.GetStdErr(), nginx.TestScopeGlobal, "") + c.JSON(http.StatusOK, result) } // TestConfigWithNamespace tests nginx config in isolated sandbox for a specific namespace @@ -73,15 +82,8 @@ func TestConfigWithNamespace(c *gin.Context) { } // Use sandbox test with namespace-specific paths - result := nginx.Control(func() (string, error) { - return nginx.SandboxTestConfigWithPaths(namespaceInfo, sitePaths, streamPaths) - }) - - c.JSON(http.StatusOK, gin.H{ - "message": result.GetOutput(), - "level": result.GetLevel(), - "namespace_id": req.NamespaceID, - }) + result := nginx.SandboxTestConfigWithPaths(namespaceInfo, sitePaths, streamPaths) + c.JSON(http.StatusOK, buildNamespaceTestConfigResponse(req.NamespaceID, result)) } // Restart restarts the nginx diff --git a/api/nginx/control_test.go b/api/nginx/control_test.go new file mode 100644 index 00000000..38782e57 --- /dev/null +++ b/api/nginx/control_test.go @@ -0,0 +1,24 @@ +package nginx + +import ( + "testing" + + internalnginx "github.com/0xJacky/Nginx-UI/internal/nginx" + "github.com/stretchr/testify/assert" +) + +func TestBuildNamespaceTestConfigResponseIncludesSandboxFields(t *testing.T) { + response := buildNamespaceTestConfigResponse(9, internalnginx.TestConfigResult{ + Message: "sandbox failed", + Level: internalnginx.Error, + TestScope: internalnginx.TestScopeNamespaceSandbox, + SandboxStatus: internalnginx.SandboxStatusFailed, + ErrorCategory: internalnginx.ErrorCategoryMissingInclude, + }) + + assert.Equal(t, uint64(9), response["namespace_id"]) + assert.Equal(t, "sandbox failed", response["message"]) + assert.Equal(t, internalnginx.TestScopeNamespaceSandbox, response["test_scope"]) + assert.Equal(t, internalnginx.SandboxStatusFailed, response["sandbox_status"]) + assert.Equal(t, internalnginx.ErrorCategoryMissingInclude, response["error_category"]) +} diff --git a/app/src/api/dns.ts b/app/src/api/dns.ts index e033a153..744fb85e 100644 --- a/app/src/api/dns.ts +++ b/app/src/api/dns.ts @@ -108,6 +108,9 @@ export const dnsApi = { listDDNS() { return http.get<{ data: DDNSDomainItem[] }>(`/dns/ddns`) }, + deleteDDNSConfig(domainId: number) { + return http.delete(`${baseDomainUrl}/${domainId}/ddns`) + }, } export type { DnsCredential } diff --git a/app/src/api/ngx.ts b/app/src/api/ngx.ts index 041a6092..ef3ff97e 100644 --- a/app/src/api/ngx.ts +++ b/app/src/api/ngx.ts @@ -111,6 +111,15 @@ export interface NgxModule { loaded: boolean } +export interface NgxTestResult { + message: string + level: number + namespace_id?: number + test_scope?: 'global' | 'namespace_sandbox' + sandbox_status?: 'ok' | 'skipped' | 'failed' + error_category?: 'missing_include' | 'sandbox_build_error' | 'syntax_error' | 'nginx_runtime_error' +} + const ngx = { build_config(ngxConfig: NgxConfig) { return http.post('/ngx/build_config', ngxConfig) @@ -144,11 +153,11 @@ const ngx = { return http.post('/nginx/restart') }, - test() { + test(): Promise { return http.post('/nginx/test') }, - test_namespace(namespace_id?: number): Promise<{ message: string, level: number, namespace_id?: number }> { + test_namespace(namespace_id?: number): Promise { return http.post('/nginx/test_namespace', { namespace_id }) }, diff --git a/app/src/components/InspectConfig/InspectConfig.vue b/app/src/components/InspectConfig/InspectConfig.vue index b9f3f869..03c15af4 100644 --- a/app/src/components/InspectConfig/InspectConfig.vue +++ b/app/src/components/InspectConfig/InspectConfig.vue @@ -1,4 +1,5 @@