[优化] 界面优化

This commit is contained in:
ryan
2026-03-13 09:21:36 +08:00
parent e34446bce8
commit 8a46a66bf5
3 changed files with 1348 additions and 1372 deletions
@@ -1,19 +1,19 @@
'use client'; 'use client';
import { useEffect, useState } from 'react'; import {useEffect, useState} from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query'; import {useQuery, useQueryClient} from '@tanstack/react-query';
import { EmptyState } from '@/components/feedback/empty-state'; import {EmptyState} from '@/components/feedback/empty-state';
import { ErrorState } from '@/components/feedback/error-state'; import {ErrorState} from '@/components/feedback/error-state';
import { InlineMessage } from '@/components/feedback/inline-message'; import {InlineMessage} from '@/components/feedback/inline-message';
import { LoadingState } from '@/components/feedback/loading-state'; import {LoadingState} from '@/components/feedback/loading-state';
import { PageHeader } from '@/components/layout/page-header'; import {PageHeader} from '@/components/layout/page-header';
import { useAuth } from '@/components/providers/auth-provider'; import {useAuth} from '@/components/providers/auth-provider';
import { AppCard } from '@/components/ui/app-card'; import {AppCard} from '@/components/ui/app-card';
import { StatusBadge } from '@/components/ui/status-badge'; import {StatusBadge} from '@/components/ui/status-badge';
import { getConfigVersionPreview } from '@/features/config-versions/api/config-versions'; import {getConfigVersionPreview} from '@/features/config-versions/api/config-versions';
import { getOptions, updateOption } from '@/features/settings/api/settings'; import {getOptions, updateOption} from '@/features/settings/api/settings';
import type { OptionItem } from '@/features/settings/types'; import type {OptionItem} from '@/features/settings/types';
import { import {
CodeBlock, CodeBlock,
PrimaryButton, PrimaryButton,
@@ -170,7 +170,7 @@ function extractMainConfigLines(content: string, matcher: RegExp) {
export function PerformancePage() { export function PerformancePage() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { user } = useAuth(); const {user} = useAuth();
const [activeTab, setActiveTab] = useState<PerformanceTab>('settings'); const [activeTab, setActiveTab] = useState<PerformanceTab>('settings');
const [feedback, setFeedback] = useState<FeedbackState | null>(null); const [feedback, setFeedback] = useState<FeedbackState | null>(null);
const [busyKey, setBusyKey] = useState<string | null>(null); const [busyKey, setBusyKey] = useState<string | null>(null);
@@ -265,7 +265,7 @@ export function PerformancePage() {
try { try {
await action(); await action();
} catch (error) { } catch (error) {
setFeedback({ tone: 'danger', message: getErrorMessage(error) }); setFeedback({tone: 'danger', message: getErrorMessage(error)});
} finally { } finally {
setBusyKey(null); setBusyKey(null);
} }
@@ -280,11 +280,11 @@ export function PerformancePage() {
} }
await Promise.all([ await Promise.all([
queryClient.invalidateQueries({ queryKey: settingsQueryKey }), queryClient.invalidateQueries({queryKey: settingsQueryKey}),
queryClient.invalidateQueries({ queryKey: previewQueryKey }), queryClient.invalidateQueries({queryKey: previewQueryKey}),
queryClient.invalidateQueries({ queryKey: ['config-versions'] }), queryClient.invalidateQueries({queryKey: ['config-versions']}),
]); ]);
setFeedback({ tone: 'success', message: successMessage }); setFeedback({tone: 'success', message: successMessage});
}; };
const handleRuntimeSave = () => { const handleRuntimeSave = () => {
@@ -564,7 +564,7 @@ export function PerformancePage() {
} }
if (optionsQuery.isLoading || previewQuery.isLoading) { if (optionsQuery.isLoading || previewQuery.isLoading) {
return <LoadingState />; return <LoadingState/>;
} }
if (optionsQuery.isError) { if (optionsQuery.isError) {
@@ -608,11 +608,11 @@ export function PerformancePage() {
<div className="space-y-6"> <div className="space-y-6">
<PageHeader <PageHeader
title="性能" title="性能"
description="从设置页拆出的 OpenResty 专属工作台。这里统一维护结构化性能参数和主配置模板。" description="统一维护Nginx结构化性能参数和主配置模板。"
/> />
{feedback ? ( {feedback ? (
<InlineMessage tone={feedback.tone} message={feedback.message} /> <InlineMessage tone={feedback.tone} message={feedback.message}/>
) : null} ) : null}
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
@@ -650,15 +650,17 @@ export function PerformancePage() {
{activeTab === 'settings' ? ( {activeTab === 'settings' ? (
<div className="space-y-6"> <div className="space-y-6">
<div className="grid gap-4 md:grid-cols-3"> <div className="grid gap-4 md:grid-cols-3">
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4"> <div
className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
<p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase"> <p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase">
主配置校验 主配置校验
</p> </p>
<div className="mt-2"> <div className="mt-2">
<StatusBadge label="发布链路受管" variant="info" /> <StatusBadge label="发布链路受管" variant="info"/>
</div> </div>
</div> </div>
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4"> <div
className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
<p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase"> <p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase">
当前预览规则数 当前预览规则数
</p> </p>
@@ -666,7 +668,8 @@ export function PerformancePage() {
{preview.route_count} 条 {preview.route_count} 条
</p> </p>
</div> </div>
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4"> <div
className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
<p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase"> <p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase">
当前模板 当前模板
</p> </p>
@@ -955,7 +958,6 @@ export function PerformancePage() {
/> />
<ToggleField <ToggleField
label="proxy_buffering" label="proxy_buffering"
description="是否启用 proxy_buffering。"
tooltip={performanceFieldTooltips.proxy_buffering} tooltip={performanceFieldTooltips.proxy_buffering}
checked={performanceFields.OpenRestyProxyBufferingEnabled} checked={performanceFields.OpenRestyProxyBufferingEnabled}
onChange={(checked) => onChange={(checked) =>
@@ -1014,84 +1016,6 @@ export function PerformancePage() {
</AppCard> </AppCard>
<div className="space-y-6"> <div className="space-y-6">
<AppCard
title="OpenResty 压缩"
description="单独维护 gzip 相关参数,避免与缓存配置混在一起。"
action={
<PrimaryButton
type="button"
onClick={handleGzipSave}
disabled={busyKey === 'performance-gzip'}
>
{busyKey === 'performance-gzip'
? '保存中...'
: '保存压缩设置'}
</PrimaryButton>
}
>
<div className="space-y-5">
<div className="grid gap-5 md:grid-cols-2">
<ToggleField
label="gzip"
description="是否启用 gzip on。"
tooltip={performanceFieldTooltips.gzip}
checked={performanceFields.OpenRestyGzipEnabled}
onChange={(checked) =>
setPerformanceFields((previous) => ({
...previous,
OpenRestyGzipEnabled: checked,
}))
}
/>
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
<p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase">
当前压缩预览
</p>
{gzipPreviewLines.length > 0 ? (
<CodeBlock className="mt-3 text-xs leading-6 whitespace-pre-wrap">
{gzipPreviewLines.join('\n')}
</CodeBlock>
) : (
<p className="mt-3 text-sm leading-6 text-[var(--foreground-secondary)]">
当前主配置预览中没有压缩指令。
</p>
)}
</div>
<ResourceField
label="gzip_min_length"
tooltip={performanceFieldTooltips.gzip_min_length}
>
<ResourceInput
type="number"
value={performanceFields.OpenRestyGzipMinLength}
onChange={(event) =>
setPerformanceFields((previous) => ({
...previous,
OpenRestyGzipMinLength: event.target.value,
}))
}
/>
</ResourceField>
<ResourceField
label="gzip_comp_level"
tooltip={performanceFieldTooltips.gzip_comp_level}
>
<ResourceInput
type="number"
min="1"
max="9"
value={performanceFields.OpenRestyGzipCompLevel}
onChange={(event) =>
setPerformanceFields((previous) => ({
...previous,
OpenRestyGzipCompLevel: event.target.value,
}))
}
/>
</ResourceField>
</div>
</div>
</AppCard>
<AppCard <AppCard
title="OpenResty 缓存" title="OpenResty 缓存"
@@ -1128,7 +1052,8 @@ export function PerformancePage() {
> >
<div className="space-y-5"> <div className="space-y-5">
<div className="grid gap-4 md:grid-cols-2"> <div className="grid gap-4 md:grid-cols-2">
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4"> <div
className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
<p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase"> <p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase">
缓存状态 缓存状态
</p> </p>
@@ -1172,7 +1097,8 @@ export function PerformancePage() {
</p> </p>
</div> </div>
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4"> <div
className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
<p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase"> <p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase">
当前缓存预览 当前缓存预览
</p> </p>
@@ -1330,7 +1256,88 @@ export function PerformancePage() {
</div> </div>
</div> </div>
</AppCard> </AppCard>
<AppCard
title="OpenResty 压缩"
description="单独维护 gzip 相关参数,避免与缓存配置混在一起。"
action={
<PrimaryButton
type="button"
onClick={handleGzipSave}
disabled={busyKey === 'performance-gzip'}
>
{busyKey === 'performance-gzip'
? '保存中...'
: '保存压缩设置'}
</PrimaryButton>
}
>
<div className="space-y-5">
<div className="grid gap-5 md:grid-cols-2">
<ToggleField
label="gzip"
description="是否启用 gzip on。"
tooltip={performanceFieldTooltips.gzip}
checked={performanceFields.OpenRestyGzipEnabled}
onChange={(checked) =>
setPerformanceFields((previous) => ({
...previous,
OpenRestyGzipEnabled: checked,
}))
}
/>
<div
className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
<p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase">
当前压缩预览
</p>
{gzipPreviewLines.length > 0 ? (
<CodeBlock className="mt-3 text-xs leading-6 whitespace-pre-wrap">
{gzipPreviewLines.join('\n')}
</CodeBlock>
) : (
<p className="mt-3 text-sm leading-6 text-[var(--foreground-secondary)]">
当前主配置预览中没有压缩指令。
</p>
)}
</div> </div>
<ResourceField
label="gzip_min_length"
tooltip={performanceFieldTooltips.gzip_min_length}
>
<ResourceInput
type="number"
value={performanceFields.OpenRestyGzipMinLength}
onChange={(event) =>
setPerformanceFields((previous) => ({
...previous,
OpenRestyGzipMinLength: event.target.value,
}))
}
/>
</ResourceField>
<ResourceField
label="gzip_comp_level"
tooltip={performanceFieldTooltips.gzip_comp_level}
>
<ResourceInput
type="number"
min="1"
max="9"
value={performanceFields.OpenRestyGzipCompLevel}
onChange={(event) =>
setPerformanceFields((previous) => ({
...previous,
OpenRestyGzipCompLevel: event.target.value,
}))
}
/>
</ResourceField>
</div>
</div>
</AppCard>
</div>
</div> </div>
</div> </div>
) : ( ) : (
@@ -1392,7 +1399,8 @@ export function PerformancePage() {
variant="info" variant="info"
/> />
</div> </div>
<CodeBlock className="max-h-[560px] overflow-auto text-xs leading-6 whitespace-pre-wrap"> <CodeBlock
className="max-h-[560px] overflow-auto text-xs leading-6 whitespace-pre-wrap">
{preview.main_config} {preview.main_config}
</CodeBlock> </CodeBlock>
</div> </div>
@@ -1064,23 +1064,6 @@ export function SettingsPage() {
/> />
</ResourceField> </ResourceField>
</AppCard> </AppCard>
<AppCard
title="性能页入口"
description="OpenResty 连接、缓冲、压缩、缓存以及主配置模板编辑已迁移到侧边栏“性能”页面。"
action={
<PrimaryButton
type="button"
onClick={() => window.location.assign('/performance')}
>
打开性能页
</PrimaryButton>
}
>
<p className="text-sm leading-6 text-[var(--foreground-secondary)]">
设置页保留 Agent、系统、限流和品牌配置;OpenResty
相关项已拆分为独立工作台,支持“设置 / 编辑”双 Tab 操作。
</p>
</AppCard>
<AppCard <AppCard
title="Discovery Token 与部署命令" title="Discovery Token 与部署命令"
description="适用于新节点首次接入。可直接复制一键安装命令。" description="适用于新节点首次接入。可直接复制一键安装命令。"
@@ -1527,23 +1510,6 @@ export function SettingsPage() {
</div> </div>
</AppCard> </AppCard>
</div> </div>
<AppCard
title="性能页说明"
description="OpenResty 反代缓冲、压缩、缓存和主配置模板编辑已迁移到独立“性能”页面。"
action={
<PrimaryButton
type="button"
onClick={() => window.location.assign('/performance')}
>
前往性能页
</PrimaryButton>
}
>
<p className="text-sm leading-6 text-[var(--foreground-secondary)]">
这样可以把 Agent 运行参数、系统开关与 OpenResty
专项配置拆开,减少设置页的混杂信息量。
</p>
</AppCard>
<AppCard <AppCard
title="请求限流设置" title="请求限流设置"
description="按来源 IP 生效,保存后立即影响 Web、API、上传下载及登录注册等敏感接口。时间单位均为秒。" description="按来源 IP 生效,保存后立即影响 Web、API、上传下载及登录注册等敏感接口。时间单位均为秒。"
@@ -156,7 +156,7 @@ export function DangerButton({
interface ToggleFieldProps { interface ToggleFieldProps {
label: string; label: string;
description: string; description?: string;
checked: boolean; checked: boolean;
disabled?: boolean; disabled?: boolean;
tooltip?: string; tooltip?: string;
@@ -185,9 +185,11 @@ export function ToggleField({
<span>{label}</span> <span>{label}</span>
{tooltip ? <FieldTooltip content={tooltip} /> : null} {tooltip ? <FieldTooltip content={tooltip} /> : null}
</span> </span>
{description ? (
<span className="block text-xs leading-5 text-[var(--foreground-secondary)]"> <span className="block text-xs leading-5 text-[var(--foreground-secondary)]">
{description} {description}
</span> </span>
) : null}
</span> </span>
</label> </label>
); );