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:
1 parent
8bea331183
commit
6d6e17745c
11 files changed
+150
-54
No files matched your search
@@ -92,6 +92,11 @@ func SetupRouter() *gin.Engine {
|
|||||||
logs.GET("/labels", logsCtrl.Labels)
|
logs.GET("/labels", logsCtrl.Labels)
|
||||||
logs.GET("/label/:name/values", logsCtrl.LabelValues)
|
logs.GET("/label/:name/values", logsCtrl.LabelValues)
|
||||||
logs.GET("/my-scope", logsCtrl.MyScope)
|
logs.GET("/my-scope", logsCtrl.MyScope)
|
||||||
|
// ⚠️ 必须是 POST:前端 logsApi.histogram 用 POST 传 body(sourceId/query/start/end)。
|
||||||
|
// 曾被误改成 GET,导致 POST /logs/histogram 全部 404,
|
||||||
|
// 表现为日志查询页直方图不显示、分析页直接报 "API endpoint not found"。
|
||||||
|
// 同时保留 GET 作为简单调试入口(参数放 query string)。
|
||||||
|
logs.POST("/histogram", logsCtrl.Histogram)
|
||||||
logs.GET("/histogram", logsCtrl.Histogram)
|
logs.GET("/histogram", logsCtrl.Histogram)
|
||||||
logs.GET("/daily-volume", logsCtrl.DailyVolume)
|
logs.GET("/daily-volume", logsCtrl.DailyVolume)
|
||||||
// 总量统计(index/stats):只读索引,用于算总页数
|
// 总量统计(index/stats):只读索引,用于算总页数
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 回归:接口的 **HTTP 方法与路径** 必须和前端调用一致。
|
||||||
|
//
|
||||||
|
// 线上事故:一次模糊匹配的 patch 把
|
||||||
|
// logs.POST("/histogram", ...) → logs.GET("/histogram", ...)
|
||||||
|
// 前端 logsApi.histogram 用的是 POST(body 里带 sourceId/query/start/end),
|
||||||
|
// 于是所有直方图请求 404:
|
||||||
|
// - 日志查询页:直方图不显示
|
||||||
|
// - 日志分析页:直接报 "API endpoint not found",整页不可用
|
||||||
|
// 这类"方法写错"编译期完全查不出来,只能靠路由表断言。
|
||||||
|
func TestCriticalRoutes(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
engine := SetupRouter()
|
||||||
|
|
||||||
|
// 期望的 method + path(注意 gin 会把 :param 保留为 :id)
|
||||||
|
expect := []struct{ method, path string }{
|
||||||
|
// 日志查询 / 分析页的数据来源:必须是 POST(前端传 body)
|
||||||
|
{"POST", "/api/v1/logs/histogram"},
|
||||||
|
{"POST", "/api/v1/logs/stats"},
|
||||||
|
{"POST", "/api/v1/logs/metrics"},
|
||||||
|
{"POST", "/api/v1/logs/query"},
|
||||||
|
{"POST", "/api/v1/logs/context"},
|
||||||
|
// 查询辅助接口:GET + query string
|
||||||
|
{"GET", "/api/v1/logs/sources"},
|
||||||
|
{"GET", "/api/v1/logs/labels"},
|
||||||
|
{"GET", "/api/v1/logs/label/:name/values"},
|
||||||
|
{"GET", "/api/v1/logs/my-scope"},
|
||||||
|
{"GET", "/api/v1/logs/daily-volume"},
|
||||||
|
{"GET", "/api/v1/logs/health"},
|
||||||
|
// 告警
|
||||||
|
{"GET", "/api/v1/alerts/rules"},
|
||||||
|
{"POST", "/api/v1/alerts/rules"},
|
||||||
|
{"GET", "/api/v1/alerts/channels"},
|
||||||
|
{"POST", "/api/v1/alerts/check"},
|
||||||
|
{"GET", "/api/v1/alerts/deliveries"},
|
||||||
|
// 审计 / 自检 / 备份
|
||||||
|
{"GET", "/api/v1/audit-logs"},
|
||||||
|
{"GET", "/api/v1/system/self-check"},
|
||||||
|
{"GET", "/api/v1/system/backup"},
|
||||||
|
}
|
||||||
|
|
||||||
|
registered := map[string]bool{}
|
||||||
|
for _, route := range engine.Routes() {
|
||||||
|
registered[route.Method+" "+route.Path] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range expect {
|
||||||
|
key := item.method + " " + item.path
|
||||||
|
if !registered[key] {
|
||||||
|
t.Errorf("缺少路由: %s(前端按这个方法和路径调用,缺了就是 404)", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回归:前端用 POST 的接口不能只有 GET 版本。
|
||||||
|
// 单独断言一次,避免以后又有人"顺手"把 POST 改成 GET。
|
||||||
|
func TestHistogramAcceptsPost(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
engine := SetupRouter()
|
||||||
|
|
||||||
|
methods := map[string]bool{}
|
||||||
|
for _, route := range engine.Routes() {
|
||||||
|
if route.Path == "/api/v1/logs/histogram" {
|
||||||
|
methods[route.Method] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !methods["POST"] {
|
||||||
|
t.Fatal("POST /api/v1/logs/histogram 未注册:前端 logsApi.histogram 用 POST,会 404")
|
||||||
|
}
|
||||||
|
if !methods["GET"] {
|
||||||
|
t.Fatal("GET /api/v1/logs/histogram 未注册:curl 调试入口丢了")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
Sun,
|
Sun,
|
||||||
Moon,
|
Moon,
|
||||||
Settings,
|
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -238,17 +237,6 @@ const Layout = memo(function Layout({ children }: { children: React.ReactNode })
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4 flex-shrink-0">
|
<div className="flex items-center gap-4 flex-shrink-0">
|
||||||
{/* 系统设置入口:低频平台配置,放右上角比占侧栏一位更合适 */}
|
|
||||||
{user?.role === 'admin' && (
|
|
||||||
<Link
|
|
||||||
to="/settings"
|
|
||||||
title={t('menu.settings')}
|
|
||||||
aria-label={t('menu.settings')}
|
|
||||||
className="flex items-center gap-1 rounded-lg px-2.5 py-1.5 text-muted-foreground transition-all hover:bg-muted hover:text-foreground"
|
|
||||||
>
|
|
||||||
<Settings className="w-4 h-4" />
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
{user && (
|
{user && (
|
||||||
<div className="flex items-center gap-3 text-sm text-muted-foreground">
|
<div className="flex items-center gap-3 text-sm text-muted-foreground">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export default {
|
|||||||
groups: {
|
groups: {
|
||||||
resources: 'Resources',
|
resources: 'Resources',
|
||||||
services: 'Log Service',
|
services: 'Log Service',
|
||||||
system: 'Users & Audit',
|
system: 'System Settings',
|
||||||
},
|
},
|
||||||
dashboard: 'Dashboard',
|
dashboard: 'Dashboard',
|
||||||
wizard: 'Deploy Wizard',
|
wizard: 'Deploy Wizard',
|
||||||
@@ -17,7 +17,9 @@ export default {
|
|||||||
logs: 'Log Query',
|
logs: 'Log Query',
|
||||||
alerts: 'Alerts',
|
alerts: 'Alerts',
|
||||||
analysis: 'Log Analysis',
|
analysis: 'Log Analysis',
|
||||||
settings: 'Settings',
|
settings: 'General',
|
||||||
|
ldap: 'LDAP',
|
||||||
|
registry: 'Image Registry',
|
||||||
users: 'Users',
|
users: 'Users',
|
||||||
audit: 'Audit Log',
|
audit: 'Audit Log',
|
||||||
login: 'Sign In',
|
login: 'Sign In',
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
// settings namespace — Settings page (general/password/about, notifications, LDAP, image registry), English copy.
|
// settings namespace — Settings page (general/password/about, notifications, LDAP, image registry), English copy.
|
||||||
// Note: theme & language card titles and ThemeSection copy are owned elsewhere and are not in this dictionary.
|
// Note: theme & language card titles and ThemeSection copy are owned elsewhere and are not in this dictionary.
|
||||||
export default {
|
export default {
|
||||||
title: 'Settings',
|
title: 'General',
|
||||||
description: 'General settings, alert notifications, LDAP integration, and the default image registry',
|
|
||||||
tabs: {
|
tabs: {
|
||||||
general: 'General',
|
general: 'General',
|
||||||
notify: 'Alert Notifications',
|
|
||||||
ldap: 'LDAP Integration',
|
ldap: 'LDAP Integration',
|
||||||
registry: 'Default Image Registry',
|
registry: 'Default Image Registry',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export default {
|
|||||||
groups: {
|
groups: {
|
||||||
resources: '资源接入',
|
resources: '资源接入',
|
||||||
services: '日志服务',
|
services: '日志服务',
|
||||||
system: '用户与审计',
|
system: '系统设置',
|
||||||
},
|
},
|
||||||
dashboard: '仪表盘',
|
dashboard: '仪表盘',
|
||||||
wizard: '部署向导',
|
wizard: '部署向导',
|
||||||
@@ -17,7 +17,9 @@ export default {
|
|||||||
logs: '日志查询',
|
logs: '日志查询',
|
||||||
alerts: '日志告警',
|
alerts: '日志告警',
|
||||||
analysis: '日志分析',
|
analysis: '日志分析',
|
||||||
settings: '系统设置',
|
settings: '通用配置',
|
||||||
|
ldap: 'LDAP 接入',
|
||||||
|
registry: '默认镜像源',
|
||||||
users: '用户管理',
|
users: '用户管理',
|
||||||
audit: '审计日志',
|
audit: '审计日志',
|
||||||
login: '登录',
|
login: '登录',
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
// settings 命名空间 — 系统设置页(通用配置/密码/关于、告警通知、LDAP 接入、镜像源)中文文案
|
// settings 命名空间 — 系统设置页(通用配置/密码/关于、告警通知、LDAP 接入、镜像源)中文文案
|
||||||
// 注意:主题卡片与语言卡片的标题、ThemeSection 内文案由其他模块负责,不在本词典内。
|
// 注意:主题卡片与语言卡片的标题、ThemeSection 内文案由其他模块负责,不在本词典内。
|
||||||
export default {
|
export default {
|
||||||
title: '系统设置',
|
title: '通用配置',
|
||||||
description: '通用配置、告警通知、LDAP 接入与默认镜像源',
|
|
||||||
tabs: {
|
tabs: {
|
||||||
general: '通用配置',
|
general: '通用配置',
|
||||||
notify: '告警通知',
|
|
||||||
ldap: 'LDAP 接入',
|
ldap: 'LDAP 接入',
|
||||||
registry: '默认镜像源',
|
registry: '默认镜像源',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,12 +10,9 @@ import { authApi } from '@/api'
|
|||||||
import { clearAuth } from '@/lib/auth-storage'
|
import { clearAuth } from '@/lib/auth-storage'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { PageContainer } from '@/components/PageContainer'
|
import { PageContainer } from '@/components/PageContainer'
|
||||||
import { LdapSection } from './components/LdapSection'
|
|
||||||
import { ThemeSection } from './components/ThemeSection'
|
import { ThemeSection } from './components/ThemeSection'
|
||||||
import { LanguageSection } from '@/i18n/LanguageSection'
|
import { LanguageSection } from '@/i18n/LanguageSection'
|
||||||
import { Palette } from 'lucide-react'
|
import { Palette } from 'lucide-react'
|
||||||
import { ImageRegistrySection } from './components/ImageRegistrySection'
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
||||||
|
|
||||||
// 构建指纹由 vite define 注入;本地 tsc/预览时回退
|
// 构建指纹由 vite define 注入;本地 tsc/预览时回退
|
||||||
const APP_VERSION = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : 'dev'
|
const APP_VERSION = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : 'dev'
|
||||||
@@ -70,16 +67,10 @@ const SettingsPage = memo(function SettingsPage() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer title={t('title')} description={t('description')}>
|
<PageContainer title={t('title')}>
|
||||||
<Tabs defaultValue="general" className="space-y-6">
|
{/* 不再有内层 tab:LDAP / 镜像源 已拆成独立路由,
|
||||||
<TabsList>
|
由左侧「系统设置」分组的顶层 tab 切换(单层 tab 更好理解)。 */}
|
||||||
<TabsTrigger value="general">{t('tabs.general')}</TabsTrigger>
|
<div className="space-y-6">
|
||||||
<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">
|
|
||||||
{/* 外观与语言(紧凑单卡:主题分段器 + 语言分段器) */}
|
{/* 外观与语言(紧凑单卡:主题分段器 + 语言分段器) */}
|
||||||
<Card className="border-border">
|
<Card className="border-border">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
@@ -176,20 +167,7 @@ const SettingsPage = memo(function SettingsPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</div>
|
||||||
|
|
||||||
<TabsContent value="notify" className="space-y-6">
|
|
||||||
{/* 告警通知通道已移到「日志告警 → 通知通道」,避免配置入口分裂 */}
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="ldap">
|
|
||||||
<LdapSection />
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="registry" className="space-y-6">
|
|
||||||
<ImageRegistrySection />
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -17,6 +17,8 @@ import {
|
|||||||
BarChart3,
|
BarChart3,
|
||||||
History,
|
History,
|
||||||
Boxes,
|
Boxes,
|
||||||
|
ShieldCheck,
|
||||||
|
Container,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type { AppRoute, MenuItem, RouteMeta } from '@/types/router';
|
import type { AppRoute, MenuItem, RouteMeta } from '@/types/router';
|
||||||
|
|
||||||
@@ -28,6 +30,8 @@ const Storage = lazy(() => import('@/pages/storage'));
|
|||||||
const Deployments = lazy(() => import('@/pages/deployments'));
|
const Deployments = lazy(() => import('@/pages/deployments'));
|
||||||
const Collectors = lazy(() => import('@/pages/collectors'));
|
const Collectors = lazy(() => import('@/pages/collectors'));
|
||||||
const SettingsPage = lazy(() => import('@/pages/settings'));
|
const SettingsPage = lazy(() => import('@/pages/settings'));
|
||||||
|
const LdapPage = lazy(() => import('@/pages/settings/ldap'));
|
||||||
|
const RegistryPage = lazy(() => import('@/pages/settings/registry'));
|
||||||
const NotFound = lazy(() => import('@/pages/not-found'));
|
const NotFound = lazy(() => import('@/pages/not-found'));
|
||||||
const WizardPage = lazy(() => import('@/pages/wizard'));
|
const WizardPage = lazy(() => import('@/pages/wizard'));
|
||||||
const LogsPage = lazy(() => import('@/pages/logs'));
|
const LogsPage = lazy(() => import('@/pages/logs'));
|
||||||
@@ -76,14 +80,23 @@ export const routes: AppRoute[] = [
|
|||||||
{
|
{
|
||||||
path: '/users', component: UsersPage, auth: true,
|
path: '/users', component: UsersPage, auth: true,
|
||||||
meta: {
|
meta: {
|
||||||
title: 'menu.users', icon: UsersRound, order: 8, adminOnly: true,
|
// 系统设置(tab 起点)/ 用户管理 / 审计日志 同属「系统设置」分组,
|
||||||
|
// 侧栏只占一个入口,页面内用 tab 切换。
|
||||||
|
title: 'menu.users', icon: UsersRound, order: 9, adminOnly: true,
|
||||||
|
groupKey: 'system',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ path: '/audit', component: AuditPage, auth: true, meta: { title: 'menu.audit', icon: History, order: 10, adminOnly: true, groupKey: 'system' } },
|
||||||
|
{ path: '/settings/ldap', component: LdapPage, auth: true, meta: { title: 'menu.ldap', icon: ShieldCheck, order: 11, adminOnly: true, groupKey: 'system' } },
|
||||||
|
{ path: '/settings/registry', component: RegistryPage, auth: true, meta: { title: 'menu.registry', icon: Container, order: 12, adminOnly: true, groupKey: 'system' } },
|
||||||
|
// 系统设置放回侧栏,并作为「系统设置」分组的 tab 起点(order 最小 = 排在第一个)
|
||||||
|
{
|
||||||
|
path: '/settings', component: SettingsPage, auth: true,
|
||||||
|
meta: {
|
||||||
|
title: 'menu.settings', icon: Settings, order: 8, adminOnly: true,
|
||||||
group: { key: 'system', label: 'menu.groups.system', icon: Settings, order: 8 },
|
group: { key: 'system', label: 'menu.groups.system', icon: Settings, order: 8 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ path: '/audit', component: AuditPage, auth: true, meta: { title: 'menu.audit', icon: History, order: 13, adminOnly: true, groupKey: 'system' } },
|
|
||||||
// 系统设置从侧栏移到右上角齿轮入口:属于低频平台配置,
|
|
||||||
// 和「用户/审计」这类日常安全管理不是一类东西,混在一个分组里太乱。
|
|
||||||
{ path: '/settings', component: SettingsPage, auth: true, meta: { title: 'menu.settings', icon: Settings, order: 12, adminOnly: true, hideInMenu: true } },
|
|
||||||
{ path: '*', component: NotFound, auth: false, meta: { title: 'menu.notFound', hideInMenu: true } },
|
{ path: '*', component: NotFound, auth: false, meta: { title: 'menu.notFound', hideInMenu: true } },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user