From 8a46a66bf57733c8217a20bdad981f6dd48173d7 Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 13 Mar 2026 09:21:36 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/performance-page.tsx | 2676 +++++++++-------- .../settings/components/settings-page.tsx | 34 - .../shared/components/resource-primitives.tsx | 10 +- 3 files changed, 1348 insertions(+), 1372 deletions(-) diff --git a/atsf_server/web/features/performance/components/performance-page.tsx b/atsf_server/web/features/performance/components/performance-page.tsx index bc9ec6a0..27a74906 100644 --- a/atsf_server/web/features/performance/components/performance-page.tsx +++ b/atsf_server/web/features/performance/components/performance-page.tsx @@ -1,1405 +1,1413 @@ 'use client'; -import { useEffect, useState } from 'react'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; +import {useEffect, useState} from 'react'; +import {useQuery, useQueryClient} from '@tanstack/react-query'; -import { EmptyState } from '@/components/feedback/empty-state'; -import { ErrorState } from '@/components/feedback/error-state'; -import { InlineMessage } from '@/components/feedback/inline-message'; -import { LoadingState } from '@/components/feedback/loading-state'; -import { PageHeader } from '@/components/layout/page-header'; -import { useAuth } from '@/components/providers/auth-provider'; -import { AppCard } from '@/components/ui/app-card'; -import { StatusBadge } from '@/components/ui/status-badge'; -import { getConfigVersionPreview } from '@/features/config-versions/api/config-versions'; -import { getOptions, updateOption } from '@/features/settings/api/settings'; -import type { OptionItem } from '@/features/settings/types'; +import {EmptyState} from '@/components/feedback/empty-state'; +import {ErrorState} from '@/components/feedback/error-state'; +import {InlineMessage} from '@/components/feedback/inline-message'; +import {LoadingState} from '@/components/feedback/loading-state'; +import {PageHeader} from '@/components/layout/page-header'; +import {useAuth} from '@/components/providers/auth-provider'; +import {AppCard} from '@/components/ui/app-card'; +import {StatusBadge} from '@/components/ui/status-badge'; +import {getConfigVersionPreview} from '@/features/config-versions/api/config-versions'; +import {getOptions, updateOption} from '@/features/settings/api/settings'; +import type {OptionItem} from '@/features/settings/types'; import { - CodeBlock, - PrimaryButton, - ResourceField, - ResourceInput, - ResourceSelect, - ResourceTextarea, - SecondaryButton, - ToggleField, + CodeBlock, + PrimaryButton, + ResourceField, + ResourceInput, + ResourceSelect, + ResourceTextarea, + SecondaryButton, + ToggleField, } from '@/features/shared/components/resource-primitives'; const settingsQueryKey = ['settings', 'options'] as const; const previewQueryKey = ['performance', 'preview'] as const; const defaultPerformanceFields = { - OpenRestyWorkerProcesses: 'auto', - OpenRestyWorkerConnections: '4096', - OpenRestyWorkerRlimitNofile: '65535', - OpenRestyEventsUse: '', - OpenRestyEventsMultiAcceptEnabled: false, - OpenRestyKeepaliveTimeout: '65', - OpenRestyKeepaliveRequests: '1000', - OpenRestyClientHeaderTimeout: '15', - OpenRestyClientBodyTimeout: '15', - OpenRestyClientMaxBodySize: '64m', - OpenRestyLargeClientHeaderBuffers: '4 16k', - OpenRestySendTimeout: '30', - OpenRestyProxyConnectTimeout: '5', - OpenRestyProxySendTimeout: '60', - OpenRestyProxyReadTimeout: '60', - OpenRestyProxyRequestBufferingEnabled: true, - OpenRestyProxyBufferingEnabled: true, - OpenRestyProxyBuffers: '16 16k', - OpenRestyProxyBufferSize: '8k', - OpenRestyProxyBusyBuffersSize: '64k', - OpenRestyGzipEnabled: true, - OpenRestyGzipMinLength: '1024', - OpenRestyGzipCompLevel: '5', - OpenRestyCacheEnabled: false, - OpenRestyCachePath: '', - OpenRestyCacheLevels: '1:2', - OpenRestyCacheInactive: '30m', - OpenRestyCacheMaxSize: '1g', - OpenRestyCacheKeyTemplate: '$scheme$proxy_host$request_uri', - OpenRestyCacheLockEnabled: true, - OpenRestyCacheLockTimeout: '5s', - OpenRestyCacheUseStale: - 'error timeout updating http_500 http_502 http_503 http_504', + OpenRestyWorkerProcesses: 'auto', + OpenRestyWorkerConnections: '4096', + OpenRestyWorkerRlimitNofile: '65535', + OpenRestyEventsUse: '', + OpenRestyEventsMultiAcceptEnabled: false, + OpenRestyKeepaliveTimeout: '65', + OpenRestyKeepaliveRequests: '1000', + OpenRestyClientHeaderTimeout: '15', + OpenRestyClientBodyTimeout: '15', + OpenRestyClientMaxBodySize: '64m', + OpenRestyLargeClientHeaderBuffers: '4 16k', + OpenRestySendTimeout: '30', + OpenRestyProxyConnectTimeout: '5', + OpenRestyProxySendTimeout: '60', + OpenRestyProxyReadTimeout: '60', + OpenRestyProxyRequestBufferingEnabled: true, + OpenRestyProxyBufferingEnabled: true, + OpenRestyProxyBuffers: '16 16k', + OpenRestyProxyBufferSize: '8k', + OpenRestyProxyBusyBuffersSize: '64k', + OpenRestyGzipEnabled: true, + OpenRestyGzipMinLength: '1024', + OpenRestyGzipCompLevel: '5', + OpenRestyCacheEnabled: false, + OpenRestyCachePath: '', + OpenRestyCacheLevels: '1:2', + OpenRestyCacheInactive: '30m', + OpenRestyCacheMaxSize: '1g', + OpenRestyCacheKeyTemplate: '$scheme$proxy_host$request_uri', + OpenRestyCacheLockEnabled: true, + OpenRestyCacheLockTimeout: '5s', + OpenRestyCacheUseStale: + 'error timeout updating http_500 http_502 http_503 http_504', }; const performanceFieldTooltips: Record = { - worker_processes: - 'Nginx worker 进程数。通常建议保持 auto,让 OpenResty 按 CPU 核数自动分配。', - worker_connections: - '每个 worker 可同时处理的最大连接数。值越大,可承载的并发连接越多。', - worker_rlimit_nofile: - '提升 worker 可打开的文件描述符上限,避免高并发下连接或文件句柄不足。', - events_use: - '指定事件驱动模型。Linux 常见是 epoll,留空时由 OpenResty 自动选择。', - multi_accept: - '开启后,worker 会尽可能一次接受多个新连接,适合高吞吐接入场景。', - keepalive_timeout: '客户端 Keep-Alive 空闲保持时间,单位秒。', - keepalive_requests: '单个长连接允许复用的最大请求数。', - client_header_timeout: '读取客户端请求头的超时时间,单位秒。', - client_body_timeout: '读取客户端请求体的超时时间,单位秒。', - client_max_body_size: - '限制客户端请求体大小,常用于上传文件大小控制,例如 64m、128m。', - large_client_header_buffers: - '控制大请求头使用的缓冲区数量和大小,例如 4 16k。', - send_timeout: '向客户端发送响应时的超时时间,单位秒。', - proxy_connect_timeout: '连接上游源站的超时时间,单位秒。', - proxy_send_timeout: '向上游发送请求的超时时间,单位秒。', - proxy_read_timeout: '等待上游返回响应的超时时间,单位秒。', - proxy_request_buffering: - '控制请求体是否先在 Nginx 侧缓冲后再转发给上游,上传和流式场景经常会用到。', - proxy_buffering: - '控制是否启用代理响应缓冲。开启后通常有更平滑的吞吐,但会增加内存占用。', - proxy_buffers: '设置代理响应缓冲区的数量和大小,例如 16 16k。', - proxy_buffer_size: '保存响应头等小块数据的基础缓冲区大小。', - proxy_busy_buffers_size: '限制 busy 状态下可同时占用的缓冲区总大小。', - gzip: '控制是否启用 gzip 压缩响应。', - gzip_min_length: - '只有响应体超过该字节数时才会启用 gzip,避免对极小响应做无意义压缩。', - gzip_comp_level: 'gzip 压缩等级,1 更省 CPU,9 压缩更高但更耗 CPU。', - proxy_cache_path: '缓存目录路径,对应 proxy_cache_path 指令中的磁盘位置。', - levels: '缓存目录层级,例如 1:2,可控制缓存文件的目录分布。', - inactive: '缓存对象在未命中访问时的失活时间,例如 30m。', - max_size: - '缓存目录允许占用的最大磁盘空间,会渲染到 proxy_cache_path 的 max_size。', - cache_key_template: '生成缓存 Key 的模板,决定不同请求如何命中同一缓存对象。', - proxy_cache_lock: - '启用后,同一缓存 Key 未命中时只允许一个请求回源,减少击穿。', - proxy_cache_lock_timeout: '等待缓存锁的最长时间,例如 5s。', - proxy_cache_use_stale: - '上游异常时允许返回旧缓存的条件列表,例如 error、timeout、http_500。', + worker_processes: + 'Nginx worker 进程数。通常建议保持 auto,让 OpenResty 按 CPU 核数自动分配。', + worker_connections: + '每个 worker 可同时处理的最大连接数。值越大,可承载的并发连接越多。', + worker_rlimit_nofile: + '提升 worker 可打开的文件描述符上限,避免高并发下连接或文件句柄不足。', + events_use: + '指定事件驱动模型。Linux 常见是 epoll,留空时由 OpenResty 自动选择。', + multi_accept: + '开启后,worker 会尽可能一次接受多个新连接,适合高吞吐接入场景。', + keepalive_timeout: '客户端 Keep-Alive 空闲保持时间,单位秒。', + keepalive_requests: '单个长连接允许复用的最大请求数。', + client_header_timeout: '读取客户端请求头的超时时间,单位秒。', + client_body_timeout: '读取客户端请求体的超时时间,单位秒。', + client_max_body_size: + '限制客户端请求体大小,常用于上传文件大小控制,例如 64m、128m。', + large_client_header_buffers: + '控制大请求头使用的缓冲区数量和大小,例如 4 16k。', + send_timeout: '向客户端发送响应时的超时时间,单位秒。', + proxy_connect_timeout: '连接上游源站的超时时间,单位秒。', + proxy_send_timeout: '向上游发送请求的超时时间,单位秒。', + proxy_read_timeout: '等待上游返回响应的超时时间,单位秒。', + proxy_request_buffering: + '控制请求体是否先在 Nginx 侧缓冲后再转发给上游,上传和流式场景经常会用到。', + proxy_buffering: + '控制是否启用代理响应缓冲。开启后通常有更平滑的吞吐,但会增加内存占用。', + proxy_buffers: '设置代理响应缓冲区的数量和大小,例如 16 16k。', + proxy_buffer_size: '保存响应头等小块数据的基础缓冲区大小。', + proxy_busy_buffers_size: '限制 busy 状态下可同时占用的缓冲区总大小。', + gzip: '控制是否启用 gzip 压缩响应。', + gzip_min_length: + '只有响应体超过该字节数时才会启用 gzip,避免对极小响应做无意义压缩。', + gzip_comp_level: 'gzip 压缩等级,1 更省 CPU,9 压缩更高但更耗 CPU。', + proxy_cache_path: '缓存目录路径,对应 proxy_cache_path 指令中的磁盘位置。', + levels: '缓存目录层级,例如 1:2,可控制缓存文件的目录分布。', + inactive: '缓存对象在未命中访问时的失活时间,例如 30m。', + max_size: + '缓存目录允许占用的最大磁盘空间,会渲染到 proxy_cache_path 的 max_size。', + cache_key_template: '生成缓存 Key 的模板,决定不同请求如何命中同一缓存对象。', + proxy_cache_lock: + '启用后,同一缓存 Key 未命中时只允许一个请求回源,减少击穿。', + proxy_cache_lock_timeout: '等待缓存锁的最长时间,例如 5s。', + proxy_cache_use_stale: + '上游异常时允许返回旧缓存的条件列表,例如 error、timeout、http_500。', }; type PerformanceTab = 'settings' | 'editor'; type FeedbackState = { - tone: 'info' | 'success' | 'danger'; - message: string; + tone: 'info' | 'success' | 'danger'; + message: string; }; function getErrorMessage(error: unknown) { - return error instanceof Error ? error.message : '请求失败,请稍后重试。'; + return error instanceof Error ? error.message : '请求失败,请稍后重试。'; } function optionsToMap(options: OptionItem[] | undefined) { - return (options ?? []).reduce>( - (accumulator, option) => { - accumulator[option.key] = option.value; - return accumulator; - }, - {}, - ); + return (options ?? []).reduce>( + (accumulator, option) => { + accumulator[option.key] = option.value; + return accumulator; + }, + {}, + ); } function toBoolean(value: string | undefined, fallback: boolean) { - if (value === undefined) { - return fallback; - } + if (value === undefined) { + return fallback; + } - return value === 'true'; + return value === 'true'; } function isPositiveInteger(value: string) { - const parsed = Number.parseInt(value, 10); - return !Number.isNaN(parsed) && parsed > 0; + const parsed = Number.parseInt(value, 10); + return !Number.isNaN(parsed) && parsed > 0; } function isSizeValue(value: string) { - return /^\d+[kKmMgG]?$/.test(value.trim()); + return /^\d+[kKmMgG]?$/.test(value.trim()); } function isProxyBuffersValue(value: string) { - return /^\d+\s+\d+[kKmMgG]?$/.test(value.trim()); + return /^\d+\s+\d+[kKmMgG]?$/.test(value.trim()); } function isDurationToken(value: string) { - return /^\d+[smhdwSMHDW]$/.test(value.trim()); + return /^\d+[smhdwSMHDW]$/.test(value.trim()); } function isCacheLevelsValue(value: string) { - return /^\d{1,2}(?::\d{1,2}){0,2}$/.test(value.trim()); + return /^\d{1,2}(?::\d{1,2}){0,2}$/.test(value.trim()); } function extractMainConfigLines(content: string, matcher: RegExp) { - return content - .split('\n') - .map((line) => line.trim()) - .filter((line) => line && matcher.test(line)); + return content + .split('\n') + .map((line) => line.trim()) + .filter((line) => line && matcher.test(line)); } export function PerformancePage() { - const queryClient = useQueryClient(); - const { user } = useAuth(); - const [activeTab, setActiveTab] = useState('settings'); - const [feedback, setFeedback] = useState(null); - const [busyKey, setBusyKey] = useState(null); - const [performanceFields, setPerformanceFields] = useState( - defaultPerformanceFields, - ); - const [templateContent, setTemplateContent] = useState(''); - - const isRoot = (user?.role ?? 0) >= 100; - - const optionsQuery = useQuery({ - queryKey: settingsQueryKey, - queryFn: getOptions, - enabled: isRoot, - }); - - const previewQuery = useQuery({ - queryKey: previewQueryKey, - queryFn: getConfigVersionPreview, - enabled: isRoot, - }); - - useEffect(() => { - if (!optionsQuery.data) { - return; - } - - const optionMap = optionsToMap(optionsQuery.data); - setPerformanceFields({ - OpenRestyWorkerProcesses: optionMap.OpenRestyWorkerProcesses ?? 'auto', - OpenRestyWorkerConnections: - optionMap.OpenRestyWorkerConnections ?? '4096', - OpenRestyWorkerRlimitNofile: - optionMap.OpenRestyWorkerRlimitNofile ?? '65535', - OpenRestyEventsUse: optionMap.OpenRestyEventsUse ?? '', - OpenRestyEventsMultiAcceptEnabled: toBoolean( - optionMap.OpenRestyEventsMultiAcceptEnabled, - false, - ), - OpenRestyKeepaliveTimeout: optionMap.OpenRestyKeepaliveTimeout ?? '65', - OpenRestyKeepaliveRequests: - optionMap.OpenRestyKeepaliveRequests ?? '1000', - OpenRestyClientHeaderTimeout: - optionMap.OpenRestyClientHeaderTimeout ?? '15', - OpenRestyClientBodyTimeout: optionMap.OpenRestyClientBodyTimeout ?? '15', - OpenRestyClientMaxBodySize: optionMap.OpenRestyClientMaxBodySize ?? '64m', - OpenRestyLargeClientHeaderBuffers: - optionMap.OpenRestyLargeClientHeaderBuffers ?? '4 16k', - OpenRestySendTimeout: optionMap.OpenRestySendTimeout ?? '30', - OpenRestyProxyConnectTimeout: - optionMap.OpenRestyProxyConnectTimeout ?? '5', - OpenRestyProxySendTimeout: optionMap.OpenRestyProxySendTimeout ?? '60', - OpenRestyProxyReadTimeout: optionMap.OpenRestyProxyReadTimeout ?? '60', - OpenRestyProxyRequestBufferingEnabled: toBoolean( - optionMap.OpenRestyProxyRequestBufferingEnabled, - true, - ), - OpenRestyProxyBufferingEnabled: toBoolean( - optionMap.OpenRestyProxyBufferingEnabled, - true, - ), - OpenRestyProxyBuffers: optionMap.OpenRestyProxyBuffers ?? '16 16k', - OpenRestyProxyBufferSize: optionMap.OpenRestyProxyBufferSize ?? '8k', - OpenRestyProxyBusyBuffersSize: - optionMap.OpenRestyProxyBusyBuffersSize ?? '64k', - OpenRestyGzipEnabled: toBoolean(optionMap.OpenRestyGzipEnabled, true), - OpenRestyGzipMinLength: optionMap.OpenRestyGzipMinLength ?? '1024', - OpenRestyGzipCompLevel: optionMap.OpenRestyGzipCompLevel ?? '5', - OpenRestyCacheEnabled: toBoolean(optionMap.OpenRestyCacheEnabled, false), - OpenRestyCachePath: optionMap.OpenRestyCachePath ?? '', - OpenRestyCacheLevels: optionMap.OpenRestyCacheLevels ?? '1:2', - OpenRestyCacheInactive: optionMap.OpenRestyCacheInactive ?? '30m', - OpenRestyCacheMaxSize: optionMap.OpenRestyCacheMaxSize ?? '1g', - OpenRestyCacheKeyTemplate: - optionMap.OpenRestyCacheKeyTemplate ?? '$scheme$proxy_host$request_uri', - OpenRestyCacheLockEnabled: toBoolean( - optionMap.OpenRestyCacheLockEnabled, - true, - ), - OpenRestyCacheLockTimeout: optionMap.OpenRestyCacheLockTimeout ?? '5s', - OpenRestyCacheUseStale: - optionMap.OpenRestyCacheUseStale ?? - 'error timeout updating http_500 http_502 http_503 http_504', - }); - setTemplateContent(optionMap.OpenRestyMainConfigTemplate ?? ''); - }, [optionsQuery.data]); - - const runBusyAction = async (key: string, action: () => Promise) => { - setBusyKey(key); - setFeedback(null); - - try { - await action(); - } catch (error) { - setFeedback({ tone: 'danger', message: getErrorMessage(error) }); - } finally { - setBusyKey(null); - } - }; - - const saveOptionEntries = async ( - entries: Array<[string, string]>, - successMessage: string, - ) => { - for (const [key, value] of entries) { - await updateOption(key, value); - } - - await Promise.all([ - queryClient.invalidateQueries({ queryKey: settingsQueryKey }), - queryClient.invalidateQueries({ queryKey: previewQueryKey }), - queryClient.invalidateQueries({ queryKey: ['config-versions'] }), - ]); - setFeedback({ tone: 'success', message: successMessage }); - }; - - const handleRuntimeSave = () => { - void runBusyAction('performance-runtime', async () => { - if ( - performanceFields.OpenRestyWorkerProcesses !== 'auto' && - !isPositiveInteger(performanceFields.OpenRestyWorkerProcesses) - ) { - throw new Error('worker_processes 必须为 auto 或大于 0 的整数。'); - } - - const integerFields = [ - ['worker_connections', performanceFields.OpenRestyWorkerConnections], - ['worker_rlimit_nofile', performanceFields.OpenRestyWorkerRlimitNofile], - ['keepalive_timeout', performanceFields.OpenRestyKeepaliveTimeout], - ['keepalive_requests', performanceFields.OpenRestyKeepaliveRequests], - [ - 'client_header_timeout', - performanceFields.OpenRestyClientHeaderTimeout, - ], - ['client_body_timeout', performanceFields.OpenRestyClientBodyTimeout], - ['send_timeout', performanceFields.OpenRestySendTimeout], - ] as const; - - for (const [label, value] of integerFields) { - if (!isPositiveInteger(value)) { - throw new Error(`${label} 必须为大于 0 的整数。`); - } - } - if (!isSizeValue(performanceFields.OpenRestyClientMaxBodySize)) { - throw new Error( - 'client_max_body_size 必须为整数或带 k/m/g 单位的大小值。', - ); - } - if ( - !isProxyBuffersValue( - performanceFields.OpenRestyLargeClientHeaderBuffers, - ) - ) { - throw new Error('large_client_header_buffers 格式必须类似 "4 16k"。'); - } - - await saveOptionEntries( - [ - [ - 'OpenRestyWorkerProcesses', - performanceFields.OpenRestyWorkerProcesses.trim(), - ], - [ - 'OpenRestyWorkerConnections', - performanceFields.OpenRestyWorkerConnections.trim(), - ], - [ - 'OpenRestyWorkerRlimitNofile', - performanceFields.OpenRestyWorkerRlimitNofile.trim(), - ], - ['OpenRestyEventsUse', performanceFields.OpenRestyEventsUse.trim()], - [ - 'OpenRestyEventsMultiAcceptEnabled', - String(performanceFields.OpenRestyEventsMultiAcceptEnabled), - ], - [ - 'OpenRestyKeepaliveTimeout', - performanceFields.OpenRestyKeepaliveTimeout.trim(), - ], - [ - 'OpenRestyKeepaliveRequests', - performanceFields.OpenRestyKeepaliveRequests.trim(), - ], - [ - 'OpenRestyClientHeaderTimeout', - performanceFields.OpenRestyClientHeaderTimeout.trim(), - ], - [ - 'OpenRestyClientBodyTimeout', - performanceFields.OpenRestyClientBodyTimeout.trim(), - ], - [ - 'OpenRestyClientMaxBodySize', - performanceFields.OpenRestyClientMaxBodySize.trim(), - ], - [ - 'OpenRestyLargeClientHeaderBuffers', - performanceFields.OpenRestyLargeClientHeaderBuffers.trim(), - ], - [ - 'OpenRestySendTimeout', - performanceFields.OpenRestySendTimeout.trim(), - ], - ], - 'OpenResty 连接与事件参数已保存。', - ); - }); - }; - - const handleProxySave = () => { - void runBusyAction('performance-proxy', async () => { - const timeoutFields = [ - performanceFields.OpenRestyProxyConnectTimeout, - performanceFields.OpenRestyProxySendTimeout, - performanceFields.OpenRestyProxyReadTimeout, - ]; - - for (const value of timeoutFields) { - if (!isPositiveInteger(value)) { - throw new Error('代理超时参数必须为大于 0 的整数秒。'); - } - } - - if (!isProxyBuffersValue(performanceFields.OpenRestyProxyBuffers)) { - throw new Error('proxy_buffers 格式必须类似 "16 16k"。'); - } - if ( - !isSizeValue(performanceFields.OpenRestyProxyBufferSize) || - !isSizeValue(performanceFields.OpenRestyProxyBusyBuffersSize) - ) { - throw new Error('缓冲大小必须为整数或带 k/m/g 单位的值。'); - } - - await saveOptionEntries( - [ - [ - 'OpenRestyProxyConnectTimeout', - performanceFields.OpenRestyProxyConnectTimeout.trim(), - ], - [ - 'OpenRestyProxySendTimeout', - performanceFields.OpenRestyProxySendTimeout.trim(), - ], - [ - 'OpenRestyProxyReadTimeout', - performanceFields.OpenRestyProxyReadTimeout.trim(), - ], - [ - 'OpenRestyProxyRequestBufferingEnabled', - String(performanceFields.OpenRestyProxyRequestBufferingEnabled), - ], - [ - 'OpenRestyProxyBufferingEnabled', - String(performanceFields.OpenRestyProxyBufferingEnabled), - ], - [ - 'OpenRestyProxyBuffers', - performanceFields.OpenRestyProxyBuffers.trim(), - ], - [ - 'OpenRestyProxyBufferSize', - performanceFields.OpenRestyProxyBufferSize.trim(), - ], - [ - 'OpenRestyProxyBusyBuffersSize', - performanceFields.OpenRestyProxyBusyBuffersSize.trim(), - ], - ], - 'OpenResty 反代缓冲参数已保存。', - ); - }); - }; - - const handleCacheSave = () => { - void runBusyAction('performance-cache', async () => { - if (performanceFields.OpenRestyCacheEnabled) { - if (!performanceFields.OpenRestyCachePath.trim()) { - throw new Error('启用缓存时必须填写 proxy_cache_path 目录。'); - } - if ( - !isCacheLevelsValue(performanceFields.OpenRestyCacheLevels) || - !isDurationToken(performanceFields.OpenRestyCacheInactive) || - !isSizeValue(performanceFields.OpenRestyCacheMaxSize) || - !isDurationToken(performanceFields.OpenRestyCacheLockTimeout) - ) { - throw new Error( - '缓存 levels、inactive、max_size 或 lock_timeout 格式不合法。', - ); - } - if (!performanceFields.OpenRestyCacheKeyTemplate.trim()) { - throw new Error('启用缓存时必须填写缓存 Key 模板。'); - } - } - - await saveOptionEntries( - [ - ['OpenRestyCachePath', performanceFields.OpenRestyCachePath.trim()], - [ - 'OpenRestyCacheLevels', - performanceFields.OpenRestyCacheLevels.trim(), - ], - [ - 'OpenRestyCacheInactive', - performanceFields.OpenRestyCacheInactive.trim(), - ], - [ - 'OpenRestyCacheMaxSize', - performanceFields.OpenRestyCacheMaxSize.trim(), - ], - [ - 'OpenRestyCacheKeyTemplate', - performanceFields.OpenRestyCacheKeyTemplate.trim(), - ], - [ - 'OpenRestyCacheLockEnabled', - String(performanceFields.OpenRestyCacheLockEnabled), - ], - [ - 'OpenRestyCacheLockTimeout', - performanceFields.OpenRestyCacheLockTimeout.trim(), - ], - [ - 'OpenRestyCacheUseStale', - performanceFields.OpenRestyCacheUseStale.trim(), - ], - [ - 'OpenRestyCacheEnabled', - String(performanceFields.OpenRestyCacheEnabled), - ], - ], - 'OpenResty 压缩与缓存参数已保存。', - ); - }); - }; - - const handleGzipSave = () => { - void runBusyAction('performance-gzip', async () => { - if (!isPositiveInteger(performanceFields.OpenRestyGzipMinLength)) { - throw new Error('gzip_min_length 必须为大于 0 的整数。'); - } - const gzipLevel = Number.parseInt( - performanceFields.OpenRestyGzipCompLevel, - 10, - ); - if (Number.isNaN(gzipLevel) || gzipLevel < 1 || gzipLevel > 9) { - throw new Error('gzip_comp_level 必须在 1 到 9 之间。'); - } - - await saveOptionEntries( - [ - [ - 'OpenRestyGzipEnabled', - String(performanceFields.OpenRestyGzipEnabled), - ], - [ - 'OpenRestyGzipMinLength', - performanceFields.OpenRestyGzipMinLength.trim(), - ], - [ - 'OpenRestyGzipCompLevel', - performanceFields.OpenRestyGzipCompLevel.trim(), - ], - ], - 'OpenResty 压缩参数已保存。', - ); - }); - }; - - const handleTemplateSave = () => { - void runBusyAction('performance-template', async () => { - await saveOptionEntries( - [['OpenRestyMainConfigTemplate', templateContent]], - 'OpenResty 主配置模板已保存。', - ); - }); - }; - - if (!isRoot) { - return ( -
- - -
+ const queryClient = useQueryClient(); + const {user} = useAuth(); + const [activeTab, setActiveTab] = useState('settings'); + const [feedback, setFeedback] = useState(null); + const [busyKey, setBusyKey] = useState(null); + const [performanceFields, setPerformanceFields] = useState( + defaultPerformanceFields, ); - } + const [templateContent, setTemplateContent] = useState(''); - if (optionsQuery.isLoading || previewQuery.isLoading) { - return ; - } + const isRoot = (user?.role ?? 0) >= 100; - if (optionsQuery.isError) { - return ( - - ); - } + const optionsQuery = useQuery({ + queryKey: settingsQueryKey, + queryFn: getOptions, + enabled: isRoot, + }); - if (previewQuery.isError) { - return ( - - ); - } + const previewQuery = useQuery({ + queryKey: previewQueryKey, + queryFn: getConfigVersionPreview, + enabled: isRoot, + }); - const preview = previewQuery.data; - if (!preview) { - return ( - - ); - } + useEffect(() => { + if (!optionsQuery.data) { + return; + } - const cachePreviewLines = extractMainConfigLines( - preview.main_config, - /proxy_cache_path|proxy_cache_key|proxy_cache_lock(?:_timeout)?|proxy_cache_use_stale/, - ); - const gzipPreviewLines = extractMainConfigLines( - preview.main_config, - /gzip(?:_| )/, - ); + const optionMap = optionsToMap(optionsQuery.data); + setPerformanceFields({ + OpenRestyWorkerProcesses: optionMap.OpenRestyWorkerProcesses ?? 'auto', + OpenRestyWorkerConnections: + optionMap.OpenRestyWorkerConnections ?? '4096', + OpenRestyWorkerRlimitNofile: + optionMap.OpenRestyWorkerRlimitNofile ?? '65535', + OpenRestyEventsUse: optionMap.OpenRestyEventsUse ?? '', + OpenRestyEventsMultiAcceptEnabled: toBoolean( + optionMap.OpenRestyEventsMultiAcceptEnabled, + false, + ), + OpenRestyKeepaliveTimeout: optionMap.OpenRestyKeepaliveTimeout ?? '65', + OpenRestyKeepaliveRequests: + optionMap.OpenRestyKeepaliveRequests ?? '1000', + OpenRestyClientHeaderTimeout: + optionMap.OpenRestyClientHeaderTimeout ?? '15', + OpenRestyClientBodyTimeout: optionMap.OpenRestyClientBodyTimeout ?? '15', + OpenRestyClientMaxBodySize: optionMap.OpenRestyClientMaxBodySize ?? '64m', + OpenRestyLargeClientHeaderBuffers: + optionMap.OpenRestyLargeClientHeaderBuffers ?? '4 16k', + OpenRestySendTimeout: optionMap.OpenRestySendTimeout ?? '30', + OpenRestyProxyConnectTimeout: + optionMap.OpenRestyProxyConnectTimeout ?? '5', + OpenRestyProxySendTimeout: optionMap.OpenRestyProxySendTimeout ?? '60', + OpenRestyProxyReadTimeout: optionMap.OpenRestyProxyReadTimeout ?? '60', + OpenRestyProxyRequestBufferingEnabled: toBoolean( + optionMap.OpenRestyProxyRequestBufferingEnabled, + true, + ), + OpenRestyProxyBufferingEnabled: toBoolean( + optionMap.OpenRestyProxyBufferingEnabled, + true, + ), + OpenRestyProxyBuffers: optionMap.OpenRestyProxyBuffers ?? '16 16k', + OpenRestyProxyBufferSize: optionMap.OpenRestyProxyBufferSize ?? '8k', + OpenRestyProxyBusyBuffersSize: + optionMap.OpenRestyProxyBusyBuffersSize ?? '64k', + OpenRestyGzipEnabled: toBoolean(optionMap.OpenRestyGzipEnabled, true), + OpenRestyGzipMinLength: optionMap.OpenRestyGzipMinLength ?? '1024', + OpenRestyGzipCompLevel: optionMap.OpenRestyGzipCompLevel ?? '5', + OpenRestyCacheEnabled: toBoolean(optionMap.OpenRestyCacheEnabled, false), + OpenRestyCachePath: optionMap.OpenRestyCachePath ?? '', + OpenRestyCacheLevels: optionMap.OpenRestyCacheLevels ?? '1:2', + OpenRestyCacheInactive: optionMap.OpenRestyCacheInactive ?? '30m', + OpenRestyCacheMaxSize: optionMap.OpenRestyCacheMaxSize ?? '1g', + OpenRestyCacheKeyTemplate: + optionMap.OpenRestyCacheKeyTemplate ?? '$scheme$proxy_host$request_uri', + OpenRestyCacheLockEnabled: toBoolean( + optionMap.OpenRestyCacheLockEnabled, + true, + ), + OpenRestyCacheLockTimeout: optionMap.OpenRestyCacheLockTimeout ?? '5s', + OpenRestyCacheUseStale: + optionMap.OpenRestyCacheUseStale ?? + 'error timeout updating http_500 http_502 http_503 http_504', + }); + setTemplateContent(optionMap.OpenRestyMainConfigTemplate ?? ''); + }, [optionsQuery.data]); - return ( -
- + const runBusyAction = async (key: string, action: () => Promise) => { + setBusyKey(key); + setFeedback(null); - {feedback ? ( - - ) : null} + try { + await action(); + } catch (error) { + setFeedback({tone: 'danger', message: getErrorMessage(error)}); + } finally { + setBusyKey(null); + } + }; -
- {[ - { - key: 'settings' as const, - label: '设置', - description: '维护连接、缓冲、压缩与缓存参数。', - }, - { - key: 'editor' as const, - label: '编辑', - description: '编辑 nginx.conf 模板并查看当前渲染结果。', - }, - ].map((tab) => ( - - ))} -
+ const saveOptionEntries = async ( + entries: Array<[string, string]>, + successMessage: string, + ) => { + for (const [key, value] of entries) { + await updateOption(key, value); + } - {activeTab === 'settings' ? ( -
-
-
-

- 主配置校验 -

-
- -
-
-
-

- 当前预览规则数 -

-

- {preview.route_count} 条 -

-
-
-

- 当前模板 -

-

- 受控模板 + 占位符渲染 -

-
-
+ await Promise.all([ + queryClient.invalidateQueries({queryKey: settingsQueryKey}), + queryClient.invalidateQueries({queryKey: previewQueryKey}), + queryClient.invalidateQueries({queryKey: ['config-versions']}), + ]); + setFeedback({tone: 'success', message: successMessage}); + }; - - {busyKey === 'performance-runtime' - ? '保存中...' - : '保存运行调优'} - + const handleRuntimeSave = () => { + void runBusyAction('performance-runtime', async () => { + if ( + performanceFields.OpenRestyWorkerProcesses !== 'auto' && + !isPositiveInteger(performanceFields.OpenRestyWorkerProcesses) + ) { + throw new Error('worker_processes 必须为 auto 或大于 0 的整数。'); } - > -
- - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyWorkerProcesses: event.target.value, - })) - } - placeholder="auto" - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyWorkerConnections: event.target.value, - })) - } - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyWorkerRlimitNofile: event.target.value, - })) - } - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyEventsUse: event.target.value, - })) - } - > - - - - - - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyEventsMultiAcceptEnabled: checked, - })) + + const integerFields = [ + ['worker_connections', performanceFields.OpenRestyWorkerConnections], + ['worker_rlimit_nofile', performanceFields.OpenRestyWorkerRlimitNofile], + ['keepalive_timeout', performanceFields.OpenRestyKeepaliveTimeout], + ['keepalive_requests', performanceFields.OpenRestyKeepaliveRequests], + [ + 'client_header_timeout', + performanceFields.OpenRestyClientHeaderTimeout, + ], + ['client_body_timeout', performanceFields.OpenRestyClientBodyTimeout], + ['send_timeout', performanceFields.OpenRestySendTimeout], + ] as const; + + for (const [label, value] of integerFields) { + if (!isPositiveInteger(value)) { + throw new Error(`${label} 必须为大于 0 的整数。`); } - /> - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyKeepaliveTimeout: event.target.value, - })) - } - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyKeepaliveRequests: event.target.value, - })) - } - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyClientHeaderTimeout: event.target.value, - })) - } - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyClientBodyTimeout: event.target.value, - })) - } - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyClientMaxBodySize: event.target.value, - })) - } - placeholder="64m" - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyLargeClientHeaderBuffers: event.target.value, - })) - } - placeholder="4 16k" - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestySendTimeout: event.target.value, - })) - } - /> - -
-
+ } + if (!isSizeValue(performanceFields.OpenRestyClientMaxBodySize)) { + throw new Error( + 'client_max_body_size 必须为整数或带 k/m/g 单位的大小值。', + ); + } + if ( + !isProxyBuffersValue( + performanceFields.OpenRestyLargeClientHeaderBuffers, + ) + ) { + throw new Error('large_client_header_buffers 格式必须类似 "4 16k"。'); + } -
- - {busyKey === 'performance-proxy' - ? '保存中...' - : '保存代理调优'} - - } - > -
- - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyProxyConnectTimeout: event.target.value, - })) - } - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyProxySendTimeout: event.target.value, - })) - } - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyProxyReadTimeout: event.target.value, - })) - } - /> - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyProxyRequestBufferingEnabled: checked, - })) - } - /> - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyProxyBufferingEnabled: checked, - })) - } - /> - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyProxyBuffers: event.target.value, - })) - } - placeholder="16 16k" - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyProxyBufferSize: event.target.value, - })) - } - placeholder="8k" - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyProxyBusyBuffersSize: event.target.value, - })) - } - placeholder="64k" - /> - -
-
+ await saveOptionEntries( + [ + [ + 'OpenRestyWorkerProcesses', + performanceFields.OpenRestyWorkerProcesses.trim(), + ], + [ + 'OpenRestyWorkerConnections', + performanceFields.OpenRestyWorkerConnections.trim(), + ], + [ + 'OpenRestyWorkerRlimitNofile', + performanceFields.OpenRestyWorkerRlimitNofile.trim(), + ], + ['OpenRestyEventsUse', performanceFields.OpenRestyEventsUse.trim()], + [ + 'OpenRestyEventsMultiAcceptEnabled', + String(performanceFields.OpenRestyEventsMultiAcceptEnabled), + ], + [ + 'OpenRestyKeepaliveTimeout', + performanceFields.OpenRestyKeepaliveTimeout.trim(), + ], + [ + 'OpenRestyKeepaliveRequests', + performanceFields.OpenRestyKeepaliveRequests.trim(), + ], + [ + 'OpenRestyClientHeaderTimeout', + performanceFields.OpenRestyClientHeaderTimeout.trim(), + ], + [ + 'OpenRestyClientBodyTimeout', + performanceFields.OpenRestyClientBodyTimeout.trim(), + ], + [ + 'OpenRestyClientMaxBodySize', + performanceFields.OpenRestyClientMaxBodySize.trim(), + ], + [ + 'OpenRestyLargeClientHeaderBuffers', + performanceFields.OpenRestyLargeClientHeaderBuffers.trim(), + ], + [ + 'OpenRestySendTimeout', + performanceFields.OpenRestySendTimeout.trim(), + ], + ], + 'OpenResty 连接与事件参数已保存。', + ); + }); + }; + const handleProxySave = () => { + void runBusyAction('performance-proxy', async () => { + const timeoutFields = [ + performanceFields.OpenRestyProxyConnectTimeout, + performanceFields.OpenRestyProxySendTimeout, + performanceFields.OpenRestyProxyReadTimeout, + ]; + + for (const value of timeoutFields) { + if (!isPositiveInteger(value)) { + throw new Error('代理超时参数必须为大于 0 的整数秒。'); + } + } + + if (!isProxyBuffersValue(performanceFields.OpenRestyProxyBuffers)) { + throw new Error('proxy_buffers 格式必须类似 "16 16k"。'); + } + if ( + !isSizeValue(performanceFields.OpenRestyProxyBufferSize) || + !isSizeValue(performanceFields.OpenRestyProxyBusyBuffersSize) + ) { + throw new Error('缓冲大小必须为整数或带 k/m/g 单位的值。'); + } + + await saveOptionEntries( + [ + [ + 'OpenRestyProxyConnectTimeout', + performanceFields.OpenRestyProxyConnectTimeout.trim(), + ], + [ + 'OpenRestyProxySendTimeout', + performanceFields.OpenRestyProxySendTimeout.trim(), + ], + [ + 'OpenRestyProxyReadTimeout', + performanceFields.OpenRestyProxyReadTimeout.trim(), + ], + [ + 'OpenRestyProxyRequestBufferingEnabled', + String(performanceFields.OpenRestyProxyRequestBufferingEnabled), + ], + [ + 'OpenRestyProxyBufferingEnabled', + String(performanceFields.OpenRestyProxyBufferingEnabled), + ], + [ + 'OpenRestyProxyBuffers', + performanceFields.OpenRestyProxyBuffers.trim(), + ], + [ + 'OpenRestyProxyBufferSize', + performanceFields.OpenRestyProxyBufferSize.trim(), + ], + [ + 'OpenRestyProxyBusyBuffersSize', + performanceFields.OpenRestyProxyBusyBuffersSize.trim(), + ], + ], + 'OpenResty 反代缓冲参数已保存。', + ); + }); + }; + + const handleCacheSave = () => { + void runBusyAction('performance-cache', async () => { + if (performanceFields.OpenRestyCacheEnabled) { + if (!performanceFields.OpenRestyCachePath.trim()) { + throw new Error('启用缓存时必须填写 proxy_cache_path 目录。'); + } + if ( + !isCacheLevelsValue(performanceFields.OpenRestyCacheLevels) || + !isDurationToken(performanceFields.OpenRestyCacheInactive) || + !isSizeValue(performanceFields.OpenRestyCacheMaxSize) || + !isDurationToken(performanceFields.OpenRestyCacheLockTimeout) + ) { + throw new Error( + '缓存 levels、inactive、max_size 或 lock_timeout 格式不合法。', + ); + } + if (!performanceFields.OpenRestyCacheKeyTemplate.trim()) { + throw new Error('启用缓存时必须填写缓存 Key 模板。'); + } + } + + await saveOptionEntries( + [ + ['OpenRestyCachePath', performanceFields.OpenRestyCachePath.trim()], + [ + 'OpenRestyCacheLevels', + performanceFields.OpenRestyCacheLevels.trim(), + ], + [ + 'OpenRestyCacheInactive', + performanceFields.OpenRestyCacheInactive.trim(), + ], + [ + 'OpenRestyCacheMaxSize', + performanceFields.OpenRestyCacheMaxSize.trim(), + ], + [ + 'OpenRestyCacheKeyTemplate', + performanceFields.OpenRestyCacheKeyTemplate.trim(), + ], + [ + 'OpenRestyCacheLockEnabled', + String(performanceFields.OpenRestyCacheLockEnabled), + ], + [ + 'OpenRestyCacheLockTimeout', + performanceFields.OpenRestyCacheLockTimeout.trim(), + ], + [ + 'OpenRestyCacheUseStale', + performanceFields.OpenRestyCacheUseStale.trim(), + ], + [ + 'OpenRestyCacheEnabled', + String(performanceFields.OpenRestyCacheEnabled), + ], + ], + 'OpenResty 压缩与缓存参数已保存。', + ); + }); + }; + + const handleGzipSave = () => { + void runBusyAction('performance-gzip', async () => { + if (!isPositiveInteger(performanceFields.OpenRestyGzipMinLength)) { + throw new Error('gzip_min_length 必须为大于 0 的整数。'); + } + const gzipLevel = Number.parseInt( + performanceFields.OpenRestyGzipCompLevel, + 10, + ); + if (Number.isNaN(gzipLevel) || gzipLevel < 1 || gzipLevel > 9) { + throw new Error('gzip_comp_level 必须在 1 到 9 之间。'); + } + + await saveOptionEntries( + [ + [ + 'OpenRestyGzipEnabled', + String(performanceFields.OpenRestyGzipEnabled), + ], + [ + 'OpenRestyGzipMinLength', + performanceFields.OpenRestyGzipMinLength.trim(), + ], + [ + 'OpenRestyGzipCompLevel', + performanceFields.OpenRestyGzipCompLevel.trim(), + ], + ], + 'OpenResty 压缩参数已保存。', + ); + }); + }; + + const handleTemplateSave = () => { + void runBusyAction('performance-template', async () => { + await saveOptionEntries( + [['OpenRestyMainConfigTemplate', templateContent]], + 'OpenResty 主配置模板已保存。', + ); + }); + }; + + if (!isRoot) { + return (
- - {busyKey === 'performance-gzip' - ? '保存中...' - : '保存压缩设置'} - - } - > -
-
- - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyGzipEnabled: checked, - })) - } - /> -
-

- 当前压缩预览 -

- {gzipPreviewLines.length > 0 ? ( - - {gzipPreviewLines.join('\n')} - - ) : ( -

- 当前主配置预览中没有压缩指令。 -

- )} -
- - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyGzipMinLength: event.target.value, - })) - } - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyGzipCompLevel: event.target.value, - })) - } - /> - -
-
-
- - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyCacheEnabled: - !previous.OpenRestyCacheEnabled, - })) - } - > - {performanceFields.OpenRestyCacheEnabled - ? '先关闭缓存' - : '先启用缓存'} - - - {busyKey === 'performance-cache' - ? '保存中...' - : performanceFields.OpenRestyCacheEnabled - ? '保存缓存设置' - : '保存为关闭状态'} - -
- } - > -
-
-
-

- 缓存状态 -

-
- - -
-

- `max_size` 会跟随 `proxy_cache_path` - 一起渲染,不会单独占一行。 -

-
- -
-

- 当前缓存预览 -

- {cachePreviewLines.length > 0 ? ( - - {cachePreviewLines.join('\n')} - - ) : ( -

- 当前主配置预览中没有缓存指令。启用缓存并保存后,会出现 - `proxy_cache_path ... max_size=...;` 这一行。 -

- )} -
-
- -
- - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyCachePath: event.target.value, - })) - } - placeholder="/var/cache/openresty/atsflare" - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyCacheLevels: event.target.value, - })) - } - placeholder="1:2" - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyCacheInactive: event.target.value, - })) - } - placeholder="30m" - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyCacheMaxSize: event.target.value, - })) - } - placeholder="1g" - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyCacheKeyTemplate: event.target.value, - })) - } - /> - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyCacheLockEnabled: checked, - })) - } - /> - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyCacheLockTimeout: event.target.value, - })) - } - placeholder="5s" - /> - - - - setPerformanceFields((previous) => ({ - ...previous, - OpenRestyCacheUseStale: event.target.value, - })) - } - /> - -
-
- + +
-
-
- ) : ( + ); + } + + if (optionsQuery.isLoading || previewQuery.isLoading) { + return ; + } + + if (optionsQuery.isError) { + return ( + + ); + } + + if (previewQuery.isError) { + return ( + + ); + } + + const preview = previewQuery.data; + if (!preview) { + return ( + + ); + } + + const cachePreviewLines = extractMainConfigLines( + preview.main_config, + /proxy_cache_path|proxy_cache_key|proxy_cache_lock(?:_timeout)?|proxy_cache_use_stale/, + ); + const gzipPreviewLines = extractMainConfigLines( + preview.main_config, + /gzip(?:_| )/, + ); + + return (
- - - void queryClient.invalidateQueries({ - queryKey: previewQueryKey, - }) - } - > - 刷新预览 - - - {busyKey === 'performance-template' - ? '保存中...' - : '保存模板'} - -
- } - > -
-
- - setTemplateContent(event.target.value)} - className="min-h-[560px] font-mono text-xs leading-6" - placeholder="请输入受控 nginx.conf 模板" - /> - -
+ -
-
-
-

- 当前渲染预览 -

-

- 基于当前结构化参数和模板生成的主配置文件。 -

-
- -
- - {preview.main_config} - -
+ {feedback ? ( + + ) : null} + +
+ {[ + { + key: 'settings' as const, + label: '设置', + description: '维护连接、缓冲、压缩与缓存参数。', + }, + { + key: 'editor' as const, + label: '编辑', + description: '编辑 nginx.conf 模板并查看当前渲染结果。', + }, + ].map((tab) => ( + + ))}
- + + {activeTab === 'settings' ? ( +
+
+
+

+ 主配置校验 +

+
+ +
+
+
+

+ 当前预览规则数 +

+

+ {preview.route_count} 条 +

+
+
+

+ 当前模板 +

+

+ 受控模板 + 占位符渲染 +

+
+
+ + + {busyKey === 'performance-runtime' + ? '保存中...' + : '保存运行调优'} + + } + > +
+ + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyWorkerProcesses: event.target.value, + })) + } + placeholder="auto" + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyWorkerConnections: event.target.value, + })) + } + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyWorkerRlimitNofile: event.target.value, + })) + } + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyEventsUse: event.target.value, + })) + } + > + + + + + + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyEventsMultiAcceptEnabled: checked, + })) + } + /> + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyKeepaliveTimeout: event.target.value, + })) + } + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyKeepaliveRequests: event.target.value, + })) + } + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyClientHeaderTimeout: event.target.value, + })) + } + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyClientBodyTimeout: event.target.value, + })) + } + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyClientMaxBodySize: event.target.value, + })) + } + placeholder="64m" + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyLargeClientHeaderBuffers: event.target.value, + })) + } + placeholder="4 16k" + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestySendTimeout: event.target.value, + })) + } + /> + +
+
+ +
+ + {busyKey === 'performance-proxy' + ? '保存中...' + : '保存代理调优'} + + } + > +
+ + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyProxyConnectTimeout: event.target.value, + })) + } + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyProxySendTimeout: event.target.value, + })) + } + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyProxyReadTimeout: event.target.value, + })) + } + /> + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyProxyRequestBufferingEnabled: checked, + })) + } + /> + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyProxyBufferingEnabled: checked, + })) + } + /> + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyProxyBuffers: event.target.value, + })) + } + placeholder="16 16k" + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyProxyBufferSize: event.target.value, + })) + } + placeholder="8k" + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyProxyBusyBuffersSize: event.target.value, + })) + } + placeholder="64k" + /> + +
+
+ +
+ + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyCacheEnabled: + !previous.OpenRestyCacheEnabled, + })) + } + > + {performanceFields.OpenRestyCacheEnabled + ? '先关闭缓存' + : '先启用缓存'} + + + {busyKey === 'performance-cache' + ? '保存中...' + : performanceFields.OpenRestyCacheEnabled + ? '保存缓存设置' + : '保存为关闭状态'} + +
+ } + > +
+
+
+

+ 缓存状态 +

+
+ + +
+

+ `max_size` 会跟随 `proxy_cache_path` + 一起渲染,不会单独占一行。 +

+
+ +
+

+ 当前缓存预览 +

+ {cachePreviewLines.length > 0 ? ( + + {cachePreviewLines.join('\n')} + + ) : ( +

+ 当前主配置预览中没有缓存指令。启用缓存并保存后,会出现 + `proxy_cache_path ... max_size=...;` 这一行。 +

+ )} +
+
+ +
+ + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyCachePath: event.target.value, + })) + } + placeholder="/var/cache/openresty/atsflare" + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyCacheLevels: event.target.value, + })) + } + placeholder="1:2" + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyCacheInactive: event.target.value, + })) + } + placeholder="30m" + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyCacheMaxSize: event.target.value, + })) + } + placeholder="1g" + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyCacheKeyTemplate: event.target.value, + })) + } + /> + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyCacheLockEnabled: checked, + })) + } + /> + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyCacheLockTimeout: event.target.value, + })) + } + placeholder="5s" + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyCacheUseStale: event.target.value, + })) + } + /> + +
+
+ + + {busyKey === 'performance-gzip' + ? '保存中...' + : '保存压缩设置'} + + } + > +
+
+ + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyGzipEnabled: checked, + })) + } + /> +
+

+ 当前压缩预览 +

+ {gzipPreviewLines.length > 0 ? ( + + {gzipPreviewLines.join('\n')} + + ) : ( +

+ 当前主配置预览中没有压缩指令。 +

+ )} +
+ + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyGzipMinLength: event.target.value, + })) + } + /> + + + + setPerformanceFields((previous) => ({ + ...previous, + OpenRestyGzipCompLevel: event.target.value, + })) + } + /> + +
+
+
+ +
+ +
+
+ ) : ( +
+ + + void queryClient.invalidateQueries({ + queryKey: previewQueryKey, + }) + } + > + 刷新预览 + + + {busyKey === 'performance-template' + ? '保存中...' + : '保存模板'} + +
+ } + > +
+
+ + setTemplateContent(event.target.value)} + className="min-h-[560px] font-mono text-xs leading-6" + placeholder="请输入受控 nginx.conf 模板" + /> + +
+ +
+
+
+

+ 当前渲染预览 +

+

+ 基于当前结构化参数和模板生成的主配置文件。 +

+
+ +
+ + {preview.main_config} + +
+
+ + + )} - )} - - ); + ); } diff --git a/atsf_server/web/features/settings/components/settings-page.tsx b/atsf_server/web/features/settings/components/settings-page.tsx index 8c735a43..55c2fcf4 100644 --- a/atsf_server/web/features/settings/components/settings-page.tsx +++ b/atsf_server/web/features/settings/components/settings-page.tsx @@ -1064,23 +1064,6 @@ export function SettingsPage() { /> - window.location.assign('/performance')} - > - 打开性能页 - - } - > -

- 设置页保留 Agent、系统、限流和品牌配置;OpenResty - 相关项已拆分为独立工作台,支持“设置 / 编辑”双 Tab 操作。 -

-
- window.location.assign('/performance')} - > - 前往性能页 - - } - > -

- 这样可以把 Agent 运行参数、系统开关与 OpenResty - 专项配置拆开,减少设置页的混杂信息量。 -

-
{label} {tooltip ? : null} - - {description} - + {description ? ( + + {description} + + ) : null} );