diff --git a/docs/docs.go b/docs/docs.go index d37cf169..00fea8aa 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -5568,6 +5568,12 @@ const docTemplate = `{ "description": "统计时间范围(小时)", "name": "hours", "in": "query" + }, + { + "type": "integer", + "description": "趋势桶分钟数(1、5 或 60,默认 60)", + "name": "bucket_minutes", + "in": "query" } ], "responses": { @@ -8815,7 +8821,7 @@ const docTemplate = `{ "SessionCookie": [] } ], - "description": "返回脱敏后的项目部署源配置与运行状态,需要管理员权限", + "description": "返回项目部署源配置与运行状态,需要管理员权限", "produces": [ "application/json" ], @@ -16874,6 +16880,9 @@ const docTemplate = `{ "observability.AccessLogOverview": { "type": "object", "properties": { + "bucket_minutes": { + "type": "integer" + }, "device_types": { "type": "array", "items": { @@ -17822,6 +17831,9 @@ const docTemplate = `{ "pages.SourceUpdateInput": { "type": "object", "properties": { + "allow_insecure": { + "type": "boolean" + }, "asset_name": { "type": "string" }, @@ -17837,15 +17849,9 @@ const docTemplate = `{ "release_tag": { "type": "string" }, - "remote_network_policy": { - "type": "string" - }, "remote_url": { "type": "string" }, - "remote_url_set": { - "type": "boolean" - }, "repository_url": { "type": "string" }, @@ -17871,6 +17877,9 @@ const docTemplate = `{ "pages.SourceView": { "type": "object", "properties": { + "allow_insecure": { + "type": "boolean" + }, "asset_name": { "type": "string" }, @@ -17880,15 +17889,9 @@ const docTemplate = `{ "check_interval_minutes": { "type": "integer" }, - "display_url": { - "type": "string" - }, "github_repository": { "type": "string" }, - "has_remote_url": { - "type": "boolean" - }, "last_applied": { "$ref": "#/definitions/pages.SourceRevisionView" }, @@ -17913,7 +17916,7 @@ const docTemplate = `{ "release_tag": { "type": "string" }, - "remote_network_policy": { + "remote_url": { "type": "string" }, "source_type": { @@ -19895,7 +19898,8 @@ const docTemplate = `{ "ip_match", "geo_match", "pow", - "ua_check" + "ua_check", + "security_check" ], "x-enum-varnames": [ "RuleNodeStart", @@ -19904,7 +19908,8 @@ const docTemplate = `{ "RuleNodeIPMatch", "RuleNodeGeoMatch", "RuleNodePoW", - "RuleNodeUACheck" + "RuleNodeUACheck", + "RuleNodeSecurityCheck" ] }, "waf.RulePosition": { diff --git a/docs/swagger.json b/docs/swagger.json index e02e9b98..577fd4d6 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -5561,6 +5561,12 @@ "description": "统计时间范围(小时)", "name": "hours", "in": "query" + }, + { + "type": "integer", + "description": "趋势桶分钟数(1、5 或 60,默认 60)", + "name": "bucket_minutes", + "in": "query" } ], "responses": { @@ -8808,7 +8814,7 @@ "SessionCookie": [] } ], - "description": "返回脱敏后的项目部署源配置与运行状态,需要管理员权限", + "description": "返回项目部署源配置与运行状态,需要管理员权限", "produces": [ "application/json" ], @@ -16867,6 +16873,9 @@ "observability.AccessLogOverview": { "type": "object", "properties": { + "bucket_minutes": { + "type": "integer" + }, "device_types": { "type": "array", "items": { @@ -17815,6 +17824,9 @@ "pages.SourceUpdateInput": { "type": "object", "properties": { + "allow_insecure": { + "type": "boolean" + }, "asset_name": { "type": "string" }, @@ -17830,15 +17842,9 @@ "release_tag": { "type": "string" }, - "remote_network_policy": { - "type": "string" - }, "remote_url": { "type": "string" }, - "remote_url_set": { - "type": "boolean" - }, "repository_url": { "type": "string" }, @@ -17864,6 +17870,9 @@ "pages.SourceView": { "type": "object", "properties": { + "allow_insecure": { + "type": "boolean" + }, "asset_name": { "type": "string" }, @@ -17873,15 +17882,9 @@ "check_interval_minutes": { "type": "integer" }, - "display_url": { - "type": "string" - }, "github_repository": { "type": "string" }, - "has_remote_url": { - "type": "boolean" - }, "last_applied": { "$ref": "#/definitions/pages.SourceRevisionView" }, @@ -17906,7 +17909,7 @@ "release_tag": { "type": "string" }, - "remote_network_policy": { + "remote_url": { "type": "string" }, "source_type": { @@ -19888,7 +19891,8 @@ "ip_match", "geo_match", "pow", - "ua_check" + "ua_check", + "security_check" ], "x-enum-varnames": [ "RuleNodeStart", @@ -19897,7 +19901,8 @@ "RuleNodeIPMatch", "RuleNodeGeoMatch", "RuleNodePoW", - "RuleNodeUACheck" + "RuleNodeUACheck", + "RuleNodeSecurityCheck" ] }, "waf.RulePosition": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 3c9e9cdf..7d5b231f 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2017,6 +2017,8 @@ definitions: type: object observability.AccessLogOverview: properties: + bucket_minutes: + type: integer device_types: items: $ref: '#/definitions/observability.DistributionItem' @@ -2637,6 +2639,8 @@ definitions: type: object pages.SourceUpdateInput: properties: + allow_insecure: + type: boolean asset_name: type: string auto_update_enabled: @@ -2647,12 +2651,8 @@ definitions: type: string release_tag: type: string - remote_network_policy: - type: string remote_url: type: string - remote_url_set: - type: boolean repository_url: type: string source_type: @@ -2669,18 +2669,16 @@ definitions: type: object pages.SourceView: properties: + allow_insecure: + type: boolean asset_name: type: string auto_update_enabled: type: boolean check_interval_minutes: type: integer - display_url: - type: string github_repository: type: string - has_remote_url: - type: boolean last_applied: $ref: '#/definitions/pages.SourceRevisionView' last_checked_at: @@ -2697,7 +2695,7 @@ definitions: type: string release_tag: type: string - remote_network_policy: + remote_url: type: string source_type: type: string @@ -4023,6 +4021,7 @@ definitions: - geo_match - pow - ua_check + - security_check type: string x-enum-varnames: - RuleNodeStart @@ -4032,6 +4031,7 @@ definitions: - RuleNodeGeoMatch - RuleNodePoW - RuleNodeUACheck + - RuleNodeSecurityCheck waf.RulePosition: properties: x: @@ -7522,6 +7522,10 @@ paths: in: query name: hours type: integer + - description: 趋势桶分钟数(1、5 或 60,默认 60) + in: query + name: bucket_minutes + type: integer produces: - application/json responses: @@ -9430,7 +9434,7 @@ paths: - openflare-pages /api/v1/d/pages/{id}/source: get: - description: 返回脱敏后的项目部署源配置与运行状态,需要管理员权限 + description: 返回项目部署源配置与运行状态,需要管理员权限 parameters: - description: 项目 ID in: path diff --git a/frontend/app/(main)/pages/components/project-editor-dialog.tsx b/frontend/app/(main)/pages/components/project-editor-dialog.tsx index 105d0ac8..25b66a38 100644 --- a/frontend/app/(main)/pages/components/project-editor-dialog.tsx +++ b/frontend/app/(main)/pages/components/project-editor-dialog.tsx @@ -2,10 +2,7 @@ import { useEffect } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; import { Loader2 } from 'lucide-react'; -import { z } from 'zod'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; @@ -17,86 +14,15 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Switch } from '@/components/ui/switch'; -import { Textarea } from '@/components/ui/textarea'; import { type PagesProject, PagesService } from '@/lib/services/openflare'; import { projectQueryKey, projectsQueryKey } from './pages-utils'; - -const pagesProjectSchema = z - .object({ - name: z.string().trim().min(1, '请输入项目名称').max(255), - slug: z.string().trim().max(255).optional().or(z.literal('')), - description: z.string().trim().max(1000).optional().or(z.literal('')), - spa_fallback_enabled: z.boolean(), - spa_fallback_path: z.string().trim(), - api_proxy_enabled: z.boolean(), - api_proxy_path: z.string().trim(), - api_proxy_pass: z.string().trim(), - api_proxy_rewrite: z.string().trim(), - root_dir: z.string().trim().max(512).optional().or(z.literal('')), - entry_file: z.string().trim().min(1, '请输入入口文件').max(512), - }) - .superRefine((data, ctx) => { - if (data.spa_fallback_enabled && !data.spa_fallback_path.startsWith('/')) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: ['spa_fallback_path'], - message: '回退路径必须以 / 开头', - }); - } - if (data.api_proxy_enabled) { - if (!data.api_proxy_path.startsWith('/')) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: ['api_proxy_path'], - message: '匹配路径必须以 / 开头', - }); - } - if (!/^https?:\/\//i.test(data.api_proxy_pass)) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: ['api_proxy_pass'], - message: '后端地址必须以 http:// 或 https:// 开头', - }); - } - } - }); - -type PagesProjectFormValues = z.infer; - -function toFormValues(project?: PagesProject | null): PagesProjectFormValues { - if (!project) { - return { - name: '', - slug: '', - description: '', - spa_fallback_enabled: false, - spa_fallback_path: '/index.html', - api_proxy_enabled: false, - api_proxy_path: '', - api_proxy_pass: '', - api_proxy_rewrite: '', - root_dir: '', - entry_file: 'index.html', - }; - } - return { - name: project.name, - slug: project.slug, - description: project.description || '', - spa_fallback_enabled: project.spa_fallback_enabled, - spa_fallback_path: project.spa_fallback_path, - api_proxy_enabled: project.api_proxy_enabled || false, - api_proxy_path: project.api_proxy_path || '', - api_proxy_pass: project.api_proxy_pass || '', - api_proxy_rewrite: project.api_proxy_rewrite || '', - root_dir: project.root_dir || '', - entry_file: project.entry_file || 'index.html', - }; -} +import { + buildProjectPayload, + ProjectFormFields, + toFormValues, + usePagesProjectForm, +} from './project-form'; interface ProjectEditorDialogProps { open: boolean; @@ -110,39 +36,15 @@ export function ProjectEditorDialog({ project, }: ProjectEditorDialogProps) { const queryClient = useQueryClient(); - const form = useForm({ - resolver: zodResolver(pagesProjectSchema), - defaultValues: toFormValues(project), - }); + const form = usePagesProjectForm(project); useEffect(() => { if (open) form.reset(toFormValues(project)); }, [form, project, open]); const mutation = useMutation({ - mutationFn: async (values: PagesProjectFormValues) => { - const payload = { - name: values.name.trim(), - slug: values.slug?.trim() || '', - description: values.description?.trim() || '', - enabled: project ? project.enabled : true, - spa_fallback_enabled: values.spa_fallback_enabled, - spa_fallback_path: values.spa_fallback_enabled - ? values.spa_fallback_path.trim() - : project?.spa_fallback_path || '/index.html', - api_proxy_enabled: values.api_proxy_enabled, - api_proxy_path: values.api_proxy_enabled - ? values.api_proxy_path.trim() - : '', - api_proxy_pass: values.api_proxy_enabled - ? values.api_proxy_pass.trim() - : '', - api_proxy_rewrite: values.api_proxy_enabled - ? values.api_proxy_rewrite.trim() - : '', - root_dir: values.root_dir?.trim() || '', - entry_file: values.entry_file.trim(), - }; + mutationFn: async (values: Parameters[0]) => { + const payload = buildProjectPayload(values, project); return project ? PagesService.updateProject(project.id, payload) : PagesService.createProject(payload); @@ -162,9 +64,6 @@ export function ProjectEditorDialog({ }, }); - const spaEnabled = form.watch('spa_fallback_enabled'); - const apiEnabled = form.watch('api_proxy_enabled'); - return ( @@ -182,113 +81,7 @@ export function ProjectEditorDialog({ className='space-y-4' onSubmit={form.handleSubmit((values) => mutation.mutate(values))} > -
-
- - - {form.formState.errors.name ? ( -

- {form.formState.errors.name.message} -

- ) : null} -
-
- - -
-
- -
- -