From 3a6652be0f043747713352bdc13a78ff366ec4c2 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 30 Jul 2026 22:08:34 +0800 Subject: [PATCH] fix: simplify top-level settings --- apps/web/src/app-shell.integration.test.tsx | 38 +++++++++++++----- apps/web/src/app-shell.tsx | 32 ++------------- apps/web/src/styles.css | 44 --------------------- 3 files changed, 31 insertions(+), 83 deletions(-) diff --git a/apps/web/src/app-shell.integration.test.tsx b/apps/web/src/app-shell.integration.test.tsx index 7e18f76..f8df1e0 100644 --- a/apps/web/src/app-shell.integration.test.tsx +++ b/apps/web/src/app-shell.integration.test.tsx @@ -3,7 +3,7 @@ import { cleanup, fireEvent, render, screen, within } from '@testing-library/rea import userEvent from '@testing-library/user-event'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import { AppShell, type InstanceContext } from './app-shell.js'; +import { AppShell, resolveRoute, type InstanceContext } from './app-shell.js'; import type { AuditDataSource } from './audit/audit-page.js'; import type { AutomationDataSource as ScheduleDataSource } from './automation/automation-page.js'; import type { EventStreamClient } from './events/event-stream-client.js'; @@ -203,7 +203,7 @@ describe('React AppShell and Fleet vertical slice', () => { expect(within(bravo).getByText('需要认证')).toBeTruthy(); }); - it('keeps every global workspace reachable and exposes settings subsections', async () => { + it('keeps every global workspace reachable and links settings directly to security', async () => { const jobsDataSource: JobsDataSource = { load: vi.fn().mockResolvedValue(emptyPage) }; const scheduleDataSource: ScheduleDataSource = { listSchedules: vi.fn().mockResolvedValue([]), @@ -234,7 +234,7 @@ describe('React AppShell and Fleet vertical slice', () => { '/automation', ); expect(within(navigation).getByRole('link', { name: '设置' }).getAttribute('href')).toBe( - '/settings/instances', + '/settings/system', ); expect( within(navigation).getByRole('link', { name: '自动化' }).getAttribute('aria-current'), @@ -254,13 +254,31 @@ describe('React AppShell and Fleet vertical slice', () => { expect(await screen.findByText(/没有审计事件符合当前查询/i)).toBeTruthy(); rerender(); - const settingsNavigation = screen.getByRole('navigation', { name: '设置导航' }); - expect(within(settingsNavigation).getByRole('link', { name: '实例管理' })).toBeTruthy(); - expect( - within(settingsNavigation) - .getByRole('link', { name: '系统与安全' }) - .getAttribute('aria-current'), - ).toBe('page'); + expect(screen.getByRole('heading', { name: '密码保护' })).toBeTruthy(); + expect(screen.queryByRole('navigation', { name: '设置导航' })).toBeNull(); + }); + + it('redirects legacy top-level instance settings without loading Fleet', () => { + expect(resolveRoute('/settings/instances')).toMatchObject({ + kind: 'redirect', + to: '/settings/system', + }); + expect(resolveRoute('/settings/instances/bravo')).toMatchObject({ + kind: 'settings-instance-detail', + params: { instanceId: 'bravo' }, + }); + + const load = vi.fn().mockResolvedValue(snapshot); + render( + , + ); + + expect(screen.getByRole('heading', { name: '密码保护' })).toBeTruthy(); + expect(load).not.toHaveBeenCalled(); }); it('hides placeholder dev version badge in the top bar', () => { diff --git a/apps/web/src/app-shell.tsx b/apps/web/src/app-shell.tsx index 739a171..10a5ebb 100644 --- a/apps/web/src/app-shell.tsx +++ b/apps/web/src/app-shell.tsx @@ -40,7 +40,6 @@ import { type NotificationsDataSource, } from './instances/notifications-module.js'; import { OtaModule, type OtaDataSource } from './instances/ota-module.js'; -import { InstanceSettingsPage } from './settings/instance-settings-page.js'; import { InstanceDetail, INSTANCE_MODULE_LABELS, @@ -69,7 +68,6 @@ export type RouteKind = | 'job-detail' | 'audit' | 'audit-detail' - | 'settings-instances' | 'settings-instance-detail' | 'settings-system' | 'not-found'; @@ -139,13 +137,14 @@ function decode(value: string): string { export function resolveRoute(input: string): ResolvedRoute { const pathname = normalize(input); if (pathname === '/') return { kind: 'redirect', pathname, to: '/fleet' }; + if (pathname === '/settings/instances') + return { kind: 'redirect', pathname, to: '/settings/system' }; const staticRoutes: Readonly> = { '/fleet': 'fleet', '/automation': 'automation', '/instances/new': 'instance-new', '/jobs': 'jobs', '/audit': 'audit', - '/settings/instances': 'settings-instances', '/settings/system': 'settings-system', }; if (staticRoutes[pathname]) return { kind: staticRoutes[pathname], pathname }; @@ -270,14 +269,6 @@ function Page({ } /> ); - if (route.kind === 'settings-instances') - return ( - - ); if (route.kind === 'settings-system') return ; if (route.kind === 'not-found') return ( @@ -405,7 +396,6 @@ function Page({ 'job-detail': '任务详情', audit: '审计', 'audit-detail': '审计事件', - 'settings-instances': '实例设置', 'settings-instance-detail': '实例设置', 'settings-system': '系统设置', }; @@ -432,7 +422,7 @@ const GLOBAL_NAVIGATION: readonly { }[] = [ { section: 'fleet', href: '/fleet', label: '节点', icon: 'grid' }, { section: 'automation', href: '/automation', label: '自动化', icon: 'jobs' }, - { section: 'settings', href: '/settings/instances', label: '设置', icon: 'settings' }, + { section: 'settings', href: '/settings/system', label: '设置', icon: 'settings' }, ]; const STREAM_LABELS = { @@ -693,22 +683,6 @@ export function AppShell({
- {currentSection === 'settings' ? ( - - ) : null} {routeInstanceId && instanceLoading ? (

正在加载实例… diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index ecc873a..234b908 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -341,30 +341,6 @@ main { background: #fbfcfd; box-shadow: 0 5px 22px rgba(20, 35, 55, 0.055); } -.settings-navigation { - display: flex; - gap: 0.3rem; - margin: -0.35rem 0 1rem; - padding-bottom: 0.75rem; - border-bottom: 1px solid var(--line); -} -.settings-navigation a { - min-height: 2.35rem; - display: inline-flex; - align-items: center; - padding: 0.4rem 0.75rem; - color: var(--muted); - border-radius: 7px; - text-decoration: none; - font-size: 0.82rem; - font-weight: 700; -} -.settings-navigation a:hover, -.settings-navigation a[aria-current='page'] { - color: var(--primary); - background: var(--primary-soft); -} - /* Shared page headers */ .fleet-heading, main > section > header, @@ -2879,26 +2855,6 @@ main { box-shadow: none; } -.settings-navigation { - width: fit-content; - gap: 0.35rem; - margin: 0 0 1.6rem; - padding: 0.35rem; - border: 2px solid #d8cdb7; - border-radius: 999px; - background: #fffdf5; -} - -.settings-navigation a { - border-radius: 999px; -} - -.settings-navigation a:hover, -.settings-navigation a[aria-current='page'] { - color: #725d42; - background: #fff0ae; -} - .fleet-heading, main > section > header, .page-heading {