diff --git a/Wavelet/frontend/components/layout/openflare-sidebar-menu.tsx b/Wavelet/frontend/components/layout/openflare-sidebar-menu.tsx
index 36f0c4b0..5fa4e741 100644
--- a/Wavelet/frontend/components/layout/openflare-sidebar-menu.tsx
+++ b/Wavelet/frontend/components/layout/openflare-sidebar-menu.tsx
@@ -14,98 +14,103 @@ import {
SidebarMenuSubButton,
SidebarMenuSubItem,
} from '@/components/ui/sidebar';
+import type {OpenFlareNavGroup} from '@/lib/navigation/openflare-nav';
import {
matchesNavPath,
- openflareNavItems,
- openflareWebsiteNavGroup,
+ openflareSidebarNav,
isNavGroupActive,
} from '@/lib/navigation/openflare-nav';
+function SidebarNavGroupMenuItem({
+ group,
+ pathname,
+ onNavigate,
+}: {
+ group: OpenFlareNavGroup;
+ pathname: string;
+ onNavigate?: () => void;
+}) {
+ const groupActive = isNavGroupActive(pathname, group);
+ const [open, setOpen] = useState(groupActive);
+
+ useEffect(() => {
+ if (groupActive) {
+ setOpen(true);
+ }
+ }, [groupActive]);
+
+ return (
+
+
+
+
+
+ {group.title}
+
+
+
+
+
+ {group.items.map((item) => (
+
+
+
+ {item.title}
+
+
+
+ ))}
+
+
+
+
+ );
+}
+
export function OpenFlareSidebarMenu({
onNavigate,
}: {
onNavigate?: () => void;
}) {
const pathname = usePathname();
- const websiteGroupActive = isNavGroupActive(pathname, openflareWebsiteNavGroup);
- const [websiteGroupOpen, setWebsiteGroupOpen] = useState(websiteGroupActive);
-
- useEffect(() => {
- if (websiteGroupActive) {
- setWebsiteGroupOpen(true);
- }
- }, [websiteGroupActive]);
-
- const renderFlatItemsBeforeGroup = openflareNavItems.slice(0, 4);
- const renderFlatItemsAfterGroup = openflareNavItems.slice(4);
return (
- {renderFlatItemsBeforeGroup.map((item) => (
-
-
-
-
- {item.title}
-
-
-
- ))}
+ {openflareSidebarNav.map((entry) => {
+ if (entry.kind === 'group') {
+ return (
+
+ );
+ }
-
-
-
+ return (
+
-
- {openflareWebsiteNavGroup.title}
-
+
+
+ {entry.title}
+
-
-
-
- {openflareWebsiteNavGroup.items.map((item) => (
-
-
-
- {item.title}
-
-
-
- ))}
-
-
-
-
-
- {renderFlatItemsAfterGroup.map((item) => (
-
-
-
-
- {item.title}
-
-
-
- ))}
+
+ );
+ })}
);
}
\ No newline at end of file
diff --git a/Wavelet/frontend/components/layout/sidebar.tsx b/Wavelet/frontend/components/layout/sidebar.tsx
index e927b3de..ae3d1648 100644
--- a/Wavelet/frontend/components/layout/sidebar.tsx
+++ b/Wavelet/frontend/components/layout/sidebar.tsx
@@ -64,7 +64,7 @@ import {
import {useUser} from "@/contexts/user-context"
import {usePublicConfig} from "@/hooks/use-public-config"
import {OpenFlareSidebarMenu} from "@/components/layout/openflare-sidebar-menu"
-import {openflareNavItems} from "@/lib/navigation/openflare-nav"
+import {openflareSidebarNav} from "@/lib/navigation/openflare-nav"
/* 导航数据 */
const data = {
@@ -295,7 +295,7 @@ export function AppSidebar({ ...props }: React.ComponentProps) {
- {openflareNavItems.length > 0 && (
+ {openflareSidebarNav.length > 0 && (
diff --git a/Wavelet/frontend/lib/navigation/openflare-nav.ts b/Wavelet/frontend/lib/navigation/openflare-nav.ts
index 6dac5414..8d227406 100644
--- a/Wavelet/frontend/lib/navigation/openflare-nav.ts
+++ b/Wavelet/frontend/lib/navigation/openflare-nav.ts
@@ -31,17 +31,9 @@ export interface OpenFlareNavGroup {
items: OpenFlareNavSubItem[];
}
-/** OpenFlare 业务控制台侧栏导航(子页面通过父级入口或页内链接访问) */
-export const openflareNavItems: OpenFlareNavItem[] = [
- {title: '数据看板', url: '/', icon: LayoutDashboard},
- {title: '节点管理', url: '/nodes', icon: Server, childUrls: ['/nodes/detail']},
- {title: '规则管理', url: '/proxy-routes', icon: Route, childUrls: ['/proxy-routes/detail']},
- {title: 'Pages', url: '/pages', icon: FileText, childUrls: ['/pages/detail']},
- {title: 'WAF', url: '/waf', icon: Shield, childUrls: ['/waf/ip-groups']},
- {title: '版本发布', url: '/config-versions', icon: GitBranch},
- {title: '访问日志', url: '/access-logs', icon: ScrollText},
- {title: '性能调优', url: '/performance', icon: Gauge},
-];
+export type OpenFlareSidebarNavEntry =
+ | ({kind: 'item'} & OpenFlareNavItem)
+ | ({kind: 'group'} & OpenFlareNavGroup);
/** 网站管理折叠组 */
export const openflareWebsiteNavGroup: OpenFlareNavGroup = {
@@ -55,6 +47,27 @@ export const openflareWebsiteNavGroup: OpenFlareNavGroup = {
],
};
+/**
+ * OpenFlare 侧栏导航顺序(单一配置源)。
+ * 调整菜单顺序或折叠组位置时,只需修改此数组。
+ */
+export const openflareSidebarNav: OpenFlareSidebarNavEntry[] = [
+ {kind: 'item', title: '数据看板', url: '/', icon: LayoutDashboard},
+ {kind: 'item', title: '节点管理', url: '/nodes', icon: Server, childUrls: ['/nodes/detail']},
+ {kind: 'item', title: '规则管理', url: '/proxy-routes', icon: Route, childUrls: ['/proxy-routes/detail']},
+ {kind: 'item', title: 'WAF', url: '/waf', icon: Shield, childUrls: ['/waf/ip-groups']},
+ {kind: 'group', ...openflareWebsiteNavGroup},
+ {kind: 'item', title: 'Pages', url: '/pages', icon: FileText, childUrls: ['/pages/detail']},
+ {kind: 'item', title: '版本发布', url: '/config-versions', icon: GitBranch},
+ {kind: 'item', title: '访问日志', url: '/access-logs', icon: ScrollText},
+ {kind: 'item', title: '性能调优', url: '/performance', icon: Gauge},
+];
+
+/** 扁平菜单项(供路由判断等逻辑复用) */
+export const openflareNavItems: OpenFlareNavItem[] = openflareSidebarNav
+ .filter((entry): entry is {kind: 'item'} & OpenFlareNavItem => entry.kind === 'item')
+ .map(({kind: _kind, ...item}) => item);
+
/** 网站模块页内二级导航 */
export const openflareWebsiteSubNav = [
{title: '网站列表', url: '/websites'},
@@ -92,9 +105,11 @@ export function isOpenFlareConsoleRoute(pathname: string): boolean {
return false;
}
- if (isNavGroupActive(pathname, openflareWebsiteNavGroup)) {
- return true;
- }
+ return openflareSidebarNav.some((entry) => {
+ if (entry.kind === 'group') {
+ return isNavGroupActive(pathname, entry);
+ }
- return openflareNavItems.some((item) => matchesNavPath(pathname, item.url, item.childUrls));
-}
+ return matchesNavPath(pathname, entry.url, entry.childUrls);
+ });
+}
\ No newline at end of file
diff --git a/docs/changelog/index.md b/docs/changelog/index.md
index 89c328d6..1908fa95 100644
--- a/docs/changelog/index.md
+++ b/docs/changelog/index.md
@@ -57,6 +57,7 @@ sidebar: false
- 节点详情 24 小时网络/磁盘 IO 趋势图对齐原版 ECharts 样式,恢复系列摘要卡与平滑面积折线图。
- 侧栏新增「网站管理」折叠组(shadcn Collapsible + SidebarMenuSub),收纳网站、证书、DNS、源站入口。
- 移除网站、证书、DNS 页面右上角互相跳转按钮,改由侧栏折叠组统一导航。
+- 侧栏导航改为 `openflareSidebarNav` 单一配置源,折叠组位置由数组顺序决定,移除 `slice(0, 4)` 硬编码切分。
- OpenFlare 前端路由去除 `/openflare` 前缀,业务页面直接挂载于 `/`(如 `/nodes`、`/websites`);侧栏移除「首页」「我的文件」;旧路径 `/openflare/*`、`/home` 永久重定向至新路径。
## [v2.3.4] - 2026-06-17