From 8afd103751399484cf3483b2af17b0bfadab7a0c Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 12 Jul 2026 15:56:45 +0800 Subject: [PATCH] fix(zone): register domain delete/update APIs and drop edit UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补全 Zone 与 Zone 域名的 update/delete 路由与业务逻辑,修复删除域名 404;前端域名列表移除编辑入口,仅保留添加与删除。 --- docs/docs.go | 254 ++++++++++++++++++ docs/swagger.json | 254 ++++++++++++++++++ docs/swagger.yaml | 156 +++++++++++ .../components/zone-domain-dialog.tsx | 172 ++++++++++-- .../components/zone-domains-table.tsx | 33 +-- internal/apps/openflare/apiutil/bind.go | 7 +- internal/apps/openflare/zone/errs.go | 2 + internal/apps/openflare/zone/logics.go | 28 ++ internal/apps/openflare/zone/logics_test.go | 18 ++ internal/apps/openflare/zone/routers.go | 110 ++++++++ internal/router/v1/openflare/register_zone.go | 4 + 11 files changed, 987 insertions(+), 51 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 789eaea2..b6c7e6a0 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -10920,6 +10920,51 @@ const docTemplate = `{ } } }, + "/api/v1/d/zones/{id}/delete": { + "post": { + "security": [ + { + "SessionCookie": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "openflare-zone" + ], + "summary": "删除 Zone", + "parameters": [ + { + "type": "integer", + "description": "Zone ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Any" + } + } + } + } + }, "/api/v1/d/zones/{id}/domains": { "post": { "security": [ @@ -10995,6 +11040,140 @@ const docTemplate = `{ } } }, + "/api/v1/d/zones/{id}/domains/{domainId}/delete": { + "post": { + "security": [ + { + "SessionCookie": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "openflare-zone" + ], + "summary": "删除 Zone 域名", + "parameters": [ + { + "type": "integer", + "description": "Zone ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "域名 ID", + "name": "domainId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Any" + } + } + } + } + }, + "/api/v1/d/zones/{id}/domains/{domainId}/update": { + "post": { + "security": [ + { + "SessionCookie": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "openflare-zone" + ], + "summary": "更新 Zone 域名", + "parameters": [ + { + "type": "integer", + "description": "Zone ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "域名 ID", + "name": "domainId", + "in": "path", + "required": true + }, + { + "description": "域名参数", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/zone.DomainInput" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Any" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.ZoneDomain" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/response.Any" + } + } + } + } + }, "/api/v1/d/zones/{id}/overview": { "get": { "security": [ @@ -11046,6 +11225,81 @@ const docTemplate = `{ } } }, + "/api/v1/d/zones/{id}/update": { + "post": { + "security": [ + { + "SessionCookie": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "openflare-zone" + ], + "summary": "更新 Zone", + "parameters": [ + { + "type": "integer", + "description": "Zone ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Zone 参数", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/zone.Input" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Any" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Zone" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/response.Any" + } + } + } + } + }, "/api/v1/oauth/callback": { "post": { "description": "接收前端传回的 state 和 code,完成 OAuth/OIDC 认证并建立会话。支持登录(login)和账号绑定(bind)两种场景。", diff --git a/docs/swagger.json b/docs/swagger.json index d3af4c5b..68a89b1b 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -10913,6 +10913,51 @@ } } }, + "/api/v1/d/zones/{id}/delete": { + "post": { + "security": [ + { + "SessionCookie": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "openflare-zone" + ], + "summary": "删除 Zone", + "parameters": [ + { + "type": "integer", + "description": "Zone ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Any" + } + } + } + } + }, "/api/v1/d/zones/{id}/domains": { "post": { "security": [ @@ -10988,6 +11033,140 @@ } } }, + "/api/v1/d/zones/{id}/domains/{domainId}/delete": { + "post": { + "security": [ + { + "SessionCookie": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "openflare-zone" + ], + "summary": "删除 Zone 域名", + "parameters": [ + { + "type": "integer", + "description": "Zone ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "域名 ID", + "name": "domainId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Any" + } + } + } + } + }, + "/api/v1/d/zones/{id}/domains/{domainId}/update": { + "post": { + "security": [ + { + "SessionCookie": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "openflare-zone" + ], + "summary": "更新 Zone 域名", + "parameters": [ + { + "type": "integer", + "description": "Zone ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "域名 ID", + "name": "domainId", + "in": "path", + "required": true + }, + { + "description": "域名参数", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/zone.DomainInput" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Any" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.ZoneDomain" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/response.Any" + } + } + } + } + }, "/api/v1/d/zones/{id}/overview": { "get": { "security": [ @@ -11039,6 +11218,81 @@ } } }, + "/api/v1/d/zones/{id}/update": { + "post": { + "security": [ + { + "SessionCookie": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "openflare-zone" + ], + "summary": "更新 Zone", + "parameters": [ + { + "type": "integer", + "description": "Zone ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Zone 参数", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/zone.Input" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Any" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Zone" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Any" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/response.Any" + } + } + } + } + }, "/api/v1/oauth/callback": { "post": { "description": "接收前端传回的 state 和 code,完成 OAuth/OIDC 认证并建立会话。支持登录(login)和账号绑定(bind)两种场景。", diff --git a/docs/swagger.yaml b/docs/swagger.yaml index da3e679d..4a4473b4 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -10546,6 +10546,34 @@ paths: summary: 创建 Zone tags: - openflare-zone + /api/v1/d/zones/{id}/delete: + post: + parameters: + - description: Zone ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.Any' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Any' + security: + - SessionCookie: [] + summary: 删除 Zone + tags: + - openflare-zone /api/v1/d/zones/{id}/domains: post: consumes: @@ -10591,6 +10619,89 @@ paths: summary: 创建 Zone 域名 tags: - openflare-zone + /api/v1/d/zones/{id}/domains/{domainId}/delete: + post: + parameters: + - description: Zone ID + in: path + name: id + required: true + type: integer + - description: 域名 ID + in: path + name: domainId + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.Any' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Any' + security: + - SessionCookie: [] + summary: 删除 Zone 域名 + tags: + - openflare-zone + /api/v1/d/zones/{id}/domains/{domainId}/update: + post: + consumes: + - application/json + parameters: + - description: Zone ID + in: path + name: id + required: true + type: integer + - description: 域名 ID + in: path + name: domainId + required: true + type: integer + - description: 域名参数 + in: body + name: body + required: true + schema: + $ref: '#/definitions/zone.DomainInput' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Any' + - properties: + data: + $ref: '#/definitions/model.ZoneDomain' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.Any' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Any' + "409": + description: Conflict + schema: + $ref: '#/definitions/response.Any' + security: + - SessionCookie: [] + summary: 更新 Zone 域名 + tags: + - openflare-zone /api/v1/d/zones/{id}/overview: get: parameters: @@ -10620,6 +10731,51 @@ paths: summary: 获取 Zone 概览 tags: - openflare-zone + /api/v1/d/zones/{id}/update: + post: + consumes: + - application/json + parameters: + - description: Zone ID + in: path + name: id + required: true + type: integer + - description: Zone 参数 + in: body + name: body + required: true + schema: + $ref: '#/definitions/zone.Input' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Any' + - properties: + data: + $ref: '#/definitions/model.Zone' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.Any' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Any' + "409": + description: Conflict + schema: + $ref: '#/definitions/response.Any' + security: + - SessionCookie: [] + summary: 更新 Zone + tags: + - openflare-zone /api/v1/oauth/{source}/authorize: get: description: 根据指定认证源名称发起 OAuth 授权,支持 purpose 参数用于区分登录和账号绑定场景。认证源必须已启用。 diff --git a/frontend/app/(main)/websites/[zoneId]/components/zone-domain-dialog.tsx b/frontend/app/(main)/websites/[zoneId]/components/zone-domain-dialog.tsx index b9ceda97..be3947a6 100644 --- a/frontend/app/(main)/websites/[zoneId]/components/zone-domain-dialog.tsx +++ b/frontend/app/(main)/websites/[zoneId]/components/zone-domain-dialog.tsx @@ -1,26 +1,152 @@ -'use client' +'use client'; -import {useEffect} from 'react' -import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' -import {zodResolver} from '@hookform/resolvers/zod' -import {useForm} from 'react-hook-form' -import {Loader2} from 'lucide-react' -import {toast} from 'sonner' -import {z} from 'zod' -import {Button} from '@/components/ui/button' -import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle} from '@/components/ui/dialog' -import {Input} from '@/components/ui/input' -import {Label} from '@/components/ui/label' -import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select' -import {Textarea} from '@/components/ui/textarea' -import {TlsCertificateService, ZoneDomainService, type ZoneDomainItem} from '@/lib/services/openflare' +import {useEffect} from 'react'; +import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; +import {zodResolver} from '@hookform/resolvers/zod'; +import {useForm} from 'react-hook-form'; +import {Loader2} from 'lucide-react'; +import {toast} from 'sonner'; +import {z} from 'zod'; -const schema = z.object({domain: z.string().trim().min(1, '请输入完整域名').refine((value) => !value.includes('*.'), 'Zone 域名不支持通配符'), cert_id: z.string(), remark: z.string().max(255)}) -type Values = z.infer -export function ZoneDomainDialog({open, onOpenChange, zoneId, domain, onSaved}: {open: boolean; onOpenChange(open: boolean): void; zoneId: number; domain?: ZoneDomainItem | null; onSaved(): Promise | void}) { - const form = useForm({resolver: zodResolver(schema), defaultValues: {domain: '', cert_id: '', remark: ''}}); const queryClient = useQueryClient() - const certificatesQuery = useQuery({queryKey: ['openflare', 'tls-certificates'], queryFn: () => TlsCertificateService.list(), enabled: open}) - useEffect(() => { if (open) form.reset({domain: domain?.domain ?? '', cert_id: domain?.cert_id?.toString() ?? '', remark: domain?.remark ?? ''}) }, [domain, form, open]) - const mutation = useMutation({mutationFn: (values: Values) => {const payload = {domain: values.domain.toLowerCase(), cert_id: values.cert_id ? Number(values.cert_id) : null, remark: values.remark.trim()}; return domain ? ZoneDomainService.update(zoneId, domain.id, payload) : ZoneDomainService.create(zoneId, payload)}, onSuccess: async () => {toast.success(domain ? '域名已更新' : '域名已添加'); await Promise.all([onSaved(), queryClient.invalidateQueries({queryKey: ['openflare', 'zones']})]); onOpenChange(false)}, onError: (error) => toast.error(error instanceof Error ? error.message : '保存失败')}) - return {domain ? '编辑 Zone 域名' : '添加 Zone 域名'}填写明确 FQDN;通配符仅可存在于所选证书的 SAN 中。
mutation.mutate(values))}>
{form.formState.errors.domain &&

{form.formState.errors.domain.message}

}