'use client'; import Link from 'next/link'; import { useEffect, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { ExternalLink, Gauge, Loader2, Save } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { toast } from 'sonner'; import { useAuth } from '@/components/providers/auth-provider'; import { EmptyStateWithBorder } from '@/components/layout/empty'; import { ErrorInline } from '@/components/layout/error'; import { LoadingStateWithBorder } from '@/components/layout/loading'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; import { OptionService } from '@/lib/services/openflare'; import { defaultPerformanceFields, entriesFromKeys, mapOptionsToFields, optionsToMap, type PerformanceFields, validateCacheFields, validateGzipFields, validateProxyFields, validateRuntimeFields, } from './components/performance-utils'; const optionsQueryKey = ['openflare', 'options'] as const; export default function PerformancePage() { const t = useTranslations('performance'); const tc = useTranslations('common'); const { user, loading: authLoading } = useAuth(); const queryClient = useQueryClient(); const [fields, setFields] = useState( defaultPerformanceFields, ); const [savingSection, setSavingSection] = useState(null); const optionsQuery = useQuery({ queryKey: optionsQueryKey, queryFn: () => OptionService.list(), enabled: !!user?.is_admin, }); useEffect(() => { if (!optionsQuery.data) return; setFields(mapOptionsToFields(optionsToMap(optionsQuery.data))); }, [optionsQuery.data]); const saveMutation = useMutation({ mutationFn: async ({ section, entries, validator, }: { section: string; entries: Array<{ key: string; value: string }>; validator?: (fields: PerformanceFields) => void; }) => { validator?.(fields); setSavingSection(section); await OptionService.updateBatch(entries); }, onSuccess: async () => { toast.success(t('saved')); await Promise.all([ queryClient.invalidateQueries({ queryKey: optionsQueryKey }), queryClient.invalidateQueries({ queryKey: ['openflare', 'config-preview'], }), queryClient.invalidateQueries({ queryKey: ['openflare', 'config-versions'], }), ]); setSavingSection(null); }, onError: (error) => { setSavingSection(null); toast.error(error instanceof Error ? error.message : t('saveFailed')); }, }); const updateField = ( key: K, value: PerformanceFields[K], ) => { setFields((prev) => ({ ...prev, [key]: value })); }; if (authLoading) { return (
); } if (!user?.is_admin) { return (
); } if (optionsQuery.isLoading) { return (
); } if (optionsQuery.isError) { return (
void optionsQuery.refetch()} />
); } return (

{t('title')}

{t('description')}

{t('runtimeTitle')} {t('runtimeDesc')}
updateField('openresty_worker_processes', v)} /> updateField('openresty_worker_connections', v)} type='number' /> updateField('openresty_worker_rlimit_nofile', v)} type='number' />
updateField('openresty_events_multi_accept_enabled', v) } /> updateField('openresty_keepalive_timeout', v)} type='number' /> updateField('openresty_client_max_body_size', v)} /> updateField('openresty_resolvers', v)} placeholder='1.1.1.1 8.8.8.8' /> updateField('openresty_default_server_return_status', v) } type='number' />
{t('proxyTitle')} {t('proxyDesc')}
updateField('openresty_proxy_connect_timeout', v)} type='number' /> updateField('openresty_proxy_read_timeout', v)} type='number' /> updateField('openresty_websocket_enabled', v)} /> updateField('openresty_http3_enabled', v)} /> updateField('openresty_proxy_buffering_enabled', v) } /> updateField('openresty_proxy_buffers', v)} />
{t('gzipTitle')} updateField('openresty_gzip_enabled', v)} /> updateField('openresty_gzip_min_length', v)} type='number' /> updateField('openresty_gzip_comp_level', v)} type='number' />
{t('cacheTitle')} {t('cacheDesc')}
updateField('openresty_cache_enabled', v)} /> updateField('openresty_cache_path', v)} disabled={!fields.openresty_cache_enabled} /> updateField('openresty_cache_levels', v)} disabled={!fields.openresty_cache_enabled} /> updateField('openresty_cache_max_size', v)} disabled={!fields.openresty_cache_enabled} />
); } function FieldInput({ label, value, onChange, type = 'text', placeholder, disabled, }: { label: string; value: string; onChange: (value: string) => void; type?: string; placeholder?: string; disabled?: boolean; }) { return (
onChange(e.target.value)} className='h-9 text-xs' />
); } function ToggleRow({ label, checked, onChange, }: { label: string; checked: boolean; onChange: (value: boolean) => void; }) { return (
); }