'use client'; 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, updateOptions } from '@/features/settings/api/settings'; import type { OptionItem } from '@/features/settings/types'; import { 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 templatePanelClassName = 'h-[560px] w-full box-border overflow-auto rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-3 font-mono text-xs leading-6'; const defaultPerformanceFields = { OpenRestyWorkerProcesses: 'auto', OpenRestyWorkerConnections: '4096', OpenRestyWorkerRlimitNofile: '65535', OpenRestyEventsUse: 'epoll', OpenRestyEventsMultiAcceptEnabled: true, OpenRestyKeepaliveTimeout: '20', OpenRestyKeepaliveRequests: '1000', OpenRestyClientHeaderTimeout: '15', OpenRestyClientBodyTimeout: '15', OpenRestyClientMaxBodySize: '64m', OpenRestyLargeClientHeaderBuffers: '4 16k', OpenRestySendTimeout: '30', OpenRestyProxyConnectTimeout: '3', OpenRestyProxySendTimeout: '60', OpenRestyProxyReadTimeout: '60', OpenRestyWebsocketEnabled: true, OpenRestyProxyRequestBufferingEnabled: false, 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$host$request_uri', OpenRestyCacheLockEnabled: true, OpenRestyCacheLockTimeout: '5s', OpenRestyCacheUseStale: 'error timeout updating http_500 http_502 http_503 http_504', OpenRestyResolvers: '', }; const performanceFieldTooltips: Record = { worker_processes: 'Nginx worker 进程数。通常建议保持 auto,让 OpenResty 按 CPU 核数自动分配。', worker_connections: '每个 worker 可同时处理的最大连接数。值越大,可承载的并发连接越多。', worker_rlimit_nofile: '提升 worker 可打开的文件描述符上限,避免高并发下连接或文件句柄不足。', events_use: '指定事件驱动模型。默认使用 epoll,Linux 高并发场景通常优先选择它。', 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: '等待上游返回响应的超时时间,单位秒。', websocket: '控制是否为反向代理规则自动注入 WebSocket 升级所需的 HTTP/1.1、Upgrade 和 Connection 头。', 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。', resolvers: '自定义 DNS 解析器,留空表示不配置。若配置,当上游源站需要动态解析且 DNS 服务器失效时,能防止容器异常挂掉。例如:1.1.1.1 8.8.8.8', }; type PerformanceTab = 'settings' | 'editor'; type FeedbackState = { tone: 'info' | 'success' | 'danger'; message: string; }; function getErrorMessage(error: unknown) { return error instanceof Error ? error.message : '请求失败,请稍后重试。'; } function optionsToMap(options: OptionItem[] | undefined) { return (options ?? []).reduce>( (accumulator, option) => { accumulator[option.key] = option.value; return accumulator; }, {}, ); } function toBoolean(value: string | undefined, fallback: boolean) { if (value === undefined) { return fallback; } return value === 'true'; } function isPositiveInteger(value: string) { const parsed = Number.parseInt(value, 10); return !Number.isNaN(parsed) && parsed > 0; } function isSizeValue(value: string) { return /^\d+[kKmMgG]?$/.test(value.trim()); } function isProxyBuffersValue(value: string) { return /^\d+\s+\d+[kKmMgG]?$/.test(value.trim()); } function isDurationToken(value: string) { return /^\d+[smhdwSMHDW]$/.test(value.trim()); } function isCacheLevelsValue(value: string) { return /^\d{1,2}(?::\d{1,2}){0,2}$/.test(value.trim()); } 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 ?? 'epoll', OpenRestyEventsMultiAcceptEnabled: toBoolean( optionMap.OpenRestyEventsMultiAcceptEnabled, true, ), OpenRestyKeepaliveTimeout: optionMap.OpenRestyKeepaliveTimeout ?? '20', 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 ?? '3', OpenRestyProxySendTimeout: optionMap.OpenRestyProxySendTimeout ?? '60', OpenRestyProxyReadTimeout: optionMap.OpenRestyProxyReadTimeout ?? '60', OpenRestyWebsocketEnabled: toBoolean( optionMap.OpenRestyWebsocketEnabled, true, ), OpenRestyProxyRequestBufferingEnabled: toBoolean( optionMap.OpenRestyProxyRequestBufferingEnabled, false, ), 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$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', OpenRestyResolvers: optionMap.OpenRestyResolvers ?? '', }); 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, ) => { await updateOptions(entries.map(([key, value]) => ({ 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(), ], ['OpenRestyResolvers', performanceFields.OpenRestyResolvers.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(), ], [ 'OpenRestyWebsocketEnabled', String(performanceFields.OpenRestyWebsocketEnabled), ], [ '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 (
); } if (optionsQuery.isLoading || previewQuery.isLoading) { return ; } if (optionsQuery.isError) { return ( ); } if (previewQuery.isError) { return ( ); } const preview = previewQuery.data; if (!preview) { return ( ); } return (
{feedback ? ( setFeedback(null)} /> ) : 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, OpenRestyResolvers: event.target.value, })) } placeholder="留空表示不配置" /> 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, OpenRestyWebsocketEnabled: checked, })) } /> 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" />
{busyKey === 'performance-gzip' ? '保存中...' : '保存'} } >
setPerformanceFields((previous) => ({ ...previous, OpenRestyGzipEnabled: checked, })) } /> 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' ? '保存中...' : '保存'}
} >
setPerformanceFields((previous) => ({ ...previous, OpenRestyCachePath: event.target.value, })) } placeholder="/var/cache/openresty/openflare" /> 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, })) } />
) : (
void queryClient.invalidateQueries({ queryKey: previewQueryKey, }) } > 刷新预览 {busyKey === 'performance-template' ? '保存中...' : '保存模板'}
} >
setTemplateContent(event.target.value)} spellCheck={false} className={`${templatePanelClassName} resize-none`} placeholder="请输入受控 nginx.conf 模板" />

当前渲染预览

{preview.main_config}
)} ); }