fix(logs): 恢复 POST /logs/histogram(直方图 404)+ 系统设置改为侧栏五分页

=== 1. 修复 /analysis 与 /logs 直方图 404(我上一轮 patch 的误伤)===
现象:分析页直接报 "API endpoint not found";日志查询页直方图不显示。
根因:37e2f0f 里一次模糊匹配的 patch 把
    logs.POST("/histogram", logsCtrl.Histogram)
  改成了
    logs.GET("/histogram", logsCtrl.Histogram)
前端 logsApi.histogram 一直用 POST(body 带 sourceId/query/start/end),
于是 POST /logs/histogram 全部 404。访问日志实证:
    POST /api/v1/logs/histogram → 404   ×7
这类"方法写错"编译期查不出来,go build / tsc 都是绿的。

修法:同时注册 POST(前端用)与 GET(curl 调试用)。

新增 backend/routers/router_test.go 守门:
  TestCriticalRoutes       断言 19 条关键路由的 method+path 组合都存在
  TestHistogramAcceptsPost 单独断言 histogram 必须同时有 POST 与 GET
以后再把 POST 写成 GET,测试会直接红。

=== 2. 系统设置放回侧栏,改成 5 个顶层 tab ===
按使用反馈调整:
  系统设置(侧栏一个入口)
    ├ 通用配置      /settings            ← tab 起点
    ├ 用户管理      /users
    ├ 审计日志      /audit
    ├ LDAP 接入     /settings/ldap       ← 新拆分
    └ 默认镜像源    /settings/registry   ← 新拆分

为什么拆:原来 /settings 页面内部已有 4 个 tab(通用/告警通知/LDAP/镜像源),
如果侧栏再加一层 tab 就是"两层 tab",点两下才知道自己在哪。现在扁平化成单层。

配套改动:
  - 新增 pages/settings/ldap.tsx、registry.tsx(各自只渲染一块内容)
  - settings 主页去掉内层 Tabs,只留通用配置(外观/语言/改密码/关于)
  - 上一版把设置放到了右上角齿轮,现在移除齿轮入口
  - 菜单文案:settings 页标题 系统设置 → 通用配置;新增 LDAP 接入 / 默认镜像源
  - 删除设置页副标题「通用配置、告警通知、LDAP 接入与默认镜像源」(用户要求去掉)
  - 删除 settings 里已不存在的 tabs.notify 文案
    (注意 settings.notify.* 这些 key 仍被告警页的 NotifySection 用着,保留)

=== 3. 告警通知已不在系统设置里 ===
它在上一轮就搬到了「日志告警 → 通知通道」tab,
本次顺带把系统设置里遗留的 tab 文案一并清掉,避免出现"看似有两处配置入口"。

=== 实测 ===
  POST /api/v1/logs/histogram → 200(修复前 404)
  go test ./... 全绿(含新增的 routers 包)
  tsc -b --force 通过
This commit is contained in:
opsx committed 2026-10-28 23:12:42 +00:00
1 parent 8bea331183
commit 6d6e17745c
11 files changed
+150 -54

No files matched your search

+5 -27
View File
@@ -10,12 +10,9 @@ import { authApi } from '@/api'
import { clearAuth } from '@/lib/auth-storage'
import { toast } from 'sonner'
import { PageContainer } from '@/components/PageContainer'
import { LdapSection } from './components/LdapSection'
import { ThemeSection } from './components/ThemeSection'
import { LanguageSection } from '@/i18n/LanguageSection'
import { Palette } from 'lucide-react'
import { ImageRegistrySection } from './components/ImageRegistrySection'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
// 构建指纹由 vite define 注入;本地 tsc/预览时回退
const APP_VERSION = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : 'dev'
@@ -70,16 +67,10 @@ const SettingsPage = memo(function SettingsPage() {
)
return (
<PageContainer title={t('title')} description={t('description')}>
<Tabs defaultValue="general" className="space-y-6">
<TabsList>
<TabsTrigger value="general">{t('tabs.general')}</TabsTrigger>
<TabsTrigger value="notify">{t('tabs.notify')}</TabsTrigger>
<TabsTrigger value="ldap">{t('tabs.ldap')}</TabsTrigger>
<TabsTrigger value="registry">{t('tabs.registry')}</TabsTrigger>
</TabsList>
<TabsContent value="general" className="space-y-6">
<PageContainer title={t('title')}>
{/* 不再有内层 tab:LDAP / 镜像源 已拆成独立路由,
由左侧「系统设置」分组的顶层 tab 切换(单层 tab 更好理解)。 */}
<div className="space-y-6">
{/* 外观与语言(紧凑单卡:主题分段器 + 语言分段器) */}
<Card className="border-border">
<CardHeader className="pb-3">
@@ -176,20 +167,7 @@ const SettingsPage = memo(function SettingsPage() {
</Card>
</div>
</TabsContent>
<TabsContent value="notify" className="space-y-6">
{/* 告警通知通道已移到「日志告警 → 通知通道」,避免配置入口分裂 */}
</TabsContent>
<TabsContent value="ldap">
<LdapSection />
</TabsContent>
<TabsContent value="registry" className="space-y-6">
<ImageRegistrySection />
</TabsContent>
</Tabs>
</div>
</PageContainer>
)
})
+16
View File
@@ -0,0 +1,16 @@
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { PageContainer } from '@/components/PageContainer'
import { LdapSection } from './components/LdapSection'
/** LDAP 接入:从系统设置里拆出来,作为侧栏「系统设置」分组的一个 tab */
export const LdapPage = memo(function LdapPage() {
const { t } = useTranslation('settings')
return (
<PageContainer title={t('tabs.ldap')}>
<LdapSection />
</PageContainer>
)
})
export default LdapPage
+16
View File
@@ -0,0 +1,16 @@
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { PageContainer } from '@/components/PageContainer'
import { ImageRegistrySection } from './components/ImageRegistrySection'
/** 默认镜像源:从系统设置里拆出来,作为侧栏「系统设置」分组的一个 tab */
export const RegistryPage = memo(function RegistryPage() {
const { t } = useTranslation('settings')
return (
<PageContainer title={t('tabs.registry')}>
<ImageRegistrySection />
</PageContainer>
)
})
export default RegistryPage