mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-12 02:06:37 +08:00
Merge branch 'main' into dev
合并侧栏「网站管理」折叠组:保留 dev 导航命名(版本发布等), 源站移入折叠组,并采用 matchesNavPath / isNavGroupActive 路由匹配逻辑。
This commit is contained in:
@@ -0,0 +1,111 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import {usePathname} from 'next/navigation';
|
||||||
|
import {useEffect, useState} from 'react';
|
||||||
|
import {ChevronRight} from 'lucide-react';
|
||||||
|
|
||||||
|
import {Collapsible, CollapsibleContent, CollapsibleTrigger} from '@/components/ui/collapsible';
|
||||||
|
import {
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
SidebarMenuSub,
|
||||||
|
SidebarMenuSubButton,
|
||||||
|
SidebarMenuSubItem,
|
||||||
|
} from '@/components/ui/sidebar';
|
||||||
|
import {
|
||||||
|
matchesNavPath,
|
||||||
|
openflareNavItems,
|
||||||
|
openflareWebsiteNavGroup,
|
||||||
|
isNavGroupActive,
|
||||||
|
} from '@/lib/navigation/openflare-nav';
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<SidebarMenu className="gap-1">
|
||||||
|
{renderFlatItemsBeforeGroup.map((item) => (
|
||||||
|
<SidebarMenuItem key={item.title}>
|
||||||
|
<SidebarMenuButton
|
||||||
|
tooltip={item.title}
|
||||||
|
isActive={matchesNavPath(pathname, item.url, item.childUrls)}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<Link href={item.url} onClick={onNavigate}>
|
||||||
|
<item.icon />
|
||||||
|
<span>{item.title}</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Collapsible
|
||||||
|
asChild
|
||||||
|
open={websiteGroupOpen}
|
||||||
|
onOpenChange={setWebsiteGroupOpen}
|
||||||
|
className="group/collapsible"
|
||||||
|
>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<SidebarMenuButton
|
||||||
|
tooltip={openflareWebsiteNavGroup.title}
|
||||||
|
isActive={websiteGroupActive}
|
||||||
|
>
|
||||||
|
<openflareWebsiteNavGroup.icon />
|
||||||
|
<span>{openflareWebsiteNavGroup.title}</span>
|
||||||
|
<ChevronRight className="ml-auto size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent>
|
||||||
|
<SidebarMenuSub>
|
||||||
|
{openflareWebsiteNavGroup.items.map((item) => (
|
||||||
|
<SidebarMenuSubItem key={item.title}>
|
||||||
|
<SidebarMenuSubButton
|
||||||
|
asChild
|
||||||
|
isActive={matchesNavPath(pathname, item.url, item.childUrls)}
|
||||||
|
>
|
||||||
|
<Link href={item.url} onClick={onNavigate}>
|
||||||
|
<span>{item.title}</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
</SidebarMenuSubItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenuSub>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</Collapsible>
|
||||||
|
|
||||||
|
{renderFlatItemsAfterGroup.map((item) => (
|
||||||
|
<SidebarMenuItem key={item.title}>
|
||||||
|
<SidebarMenuButton
|
||||||
|
tooltip={item.title}
|
||||||
|
isActive={matchesNavPath(pathname, item.url, item.childUrls)}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<Link href={item.url} onClick={onNavigate}>
|
||||||
|
<item.icon />
|
||||||
|
<span>{item.title}</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -63,6 +63,7 @@ import {
|
|||||||
|
|
||||||
import {useUser} from "@/contexts/user-context"
|
import {useUser} from "@/contexts/user-context"
|
||||||
import {usePublicConfig} from "@/hooks/use-public-config"
|
import {usePublicConfig} from "@/hooks/use-public-config"
|
||||||
|
import {OpenFlareSidebarMenu} from "@/components/layout/openflare-sidebar-menu"
|
||||||
import {openflareNavItems} from "@/lib/navigation/openflare-nav"
|
import {openflareNavItems} from "@/lib/navigation/openflare-nav"
|
||||||
|
|
||||||
/* 导航数据 */
|
/* 导航数据 */
|
||||||
@@ -297,26 +298,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
{openflareNavItems.length > 0 && (
|
{openflareNavItems.length > 0 && (
|
||||||
<SidebarGroup className="py-0">
|
<SidebarGroup className="py-0">
|
||||||
<SidebarGroupContent className="py-1">
|
<SidebarGroupContent className="py-1">
|
||||||
<SidebarMenu className="gap-1">
|
<OpenFlareSidebarMenu onNavigate={handleCloseSidebar} />
|
||||||
{openflareNavItems.map((item) => (
|
|
||||||
<SidebarMenuItem key={item.title}>
|
|
||||||
<SidebarMenuButton
|
|
||||||
tooltip={item.title}
|
|
||||||
isActive={
|
|
||||||
item.url === "/"
|
|
||||||
? pathname === "/"
|
|
||||||
: pathname === item.url || pathname.startsWith(`${item.url}/`)
|
|
||||||
}
|
|
||||||
asChild
|
|
||||||
>
|
|
||||||
<Link href={item.url} onClick={handleCloseSidebar}>
|
|
||||||
<item.icon />
|
|
||||||
<span>{item.title}</span>
|
|
||||||
</Link>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
))}
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import {Collapsible as CollapsiblePrimitive} from "radix-ui"
|
||||||
|
|
||||||
|
function Collapsible({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
||||||
|
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function CollapsibleTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CollapsiblePrimitive.Trigger>) {
|
||||||
|
return (
|
||||||
|
<CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CollapsibleContent({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CollapsiblePrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<CollapsiblePrimitive.Content data-slot="collapsible-content" {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
GitBranch,
|
GitBranch,
|
||||||
Globe,
|
Globe,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
MapPin,
|
|
||||||
Route,
|
Route,
|
||||||
ScrollText,
|
ScrollText,
|
||||||
Server,
|
Server,
|
||||||
@@ -20,29 +19,42 @@ export interface OpenFlareNavItem {
|
|||||||
childUrls?: string[];
|
childUrls?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OpenFlareNavSubItem {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
childUrls?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OpenFlareNavGroup {
|
||||||
|
title: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
items: OpenFlareNavSubItem[];
|
||||||
|
}
|
||||||
|
|
||||||
/** OpenFlare 业务控制台侧栏导航(子页面通过父级入口或页内链接访问) */
|
/** OpenFlare 业务控制台侧栏导航(子页面通过父级入口或页内链接访问) */
|
||||||
export const openflareNavItems: OpenFlareNavItem[] = [
|
export const openflareNavItems: OpenFlareNavItem[] = [
|
||||||
{title: '数据看板', url: '/', icon: LayoutDashboard},
|
{title: '数据看板', url: '/', icon: LayoutDashboard},
|
||||||
{title: '节点管理', url: '/nodes', icon: Server, childUrls: ['/nodes/detail']},
|
{title: '节点管理', url: '/nodes', icon: Server, childUrls: ['/nodes/detail']},
|
||||||
{title: '规则管理', url: '/proxy-routes', icon: Route, childUrls: ['/proxy-routes/detail']},
|
{title: '规则管理', url: '/proxy-routes', icon: Route, childUrls: ['/proxy-routes/detail']},
|
||||||
{title: 'Pages', url: '/pages', icon: FileText, childUrls: ['/pages/detail']},
|
{title: 'Pages', url: '/pages', icon: FileText, childUrls: ['/pages/detail']},
|
||||||
{
|
|
||||||
title: '网站',
|
|
||||||
url: '/websites',
|
|
||||||
icon: Globe,
|
|
||||||
childUrls: [
|
|
||||||
'/websites/detail',
|
|
||||||
'/websites/certificates',
|
|
||||||
'/websites/dns-accounts',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{title: 'WAF', url: '/waf', icon: Shield, childUrls: ['/waf/ip-groups']},
|
{title: 'WAF', url: '/waf', icon: Shield, childUrls: ['/waf/ip-groups']},
|
||||||
{title: '源站', url: '/origins', icon: MapPin, childUrls: ['/origins/detail']},
|
|
||||||
{title: '版本发布', url: '/config-versions', icon: GitBranch},
|
{title: '版本发布', url: '/config-versions', icon: GitBranch},
|
||||||
{title: '访问日志', url: '/access-logs', icon: ScrollText},
|
{title: '访问日志', url: '/access-logs', icon: ScrollText},
|
||||||
{title: '性能调优', url: '/performance', icon: Gauge},
|
{title: '性能调优', url: '/performance', icon: Gauge},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** 网站管理折叠组 */
|
||||||
|
export const openflareWebsiteNavGroup: OpenFlareNavGroup = {
|
||||||
|
title: '网站管理',
|
||||||
|
icon: Globe,
|
||||||
|
items: [
|
||||||
|
{title: '网站', url: '/websites', childUrls: ['/websites/detail']},
|
||||||
|
{title: '证书', url: '/websites/certificates'},
|
||||||
|
{title: 'DNS', url: '/websites/dns-accounts'},
|
||||||
|
{title: '源站', url: '/origins', childUrls: ['/origins/detail']},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
/** 网站模块页内二级导航 */
|
/** 网站模块页内二级导航 */
|
||||||
export const openflareWebsiteSubNav = [
|
export const openflareWebsiteSubNav = [
|
||||||
{title: '网站列表', url: '/websites'},
|
{title: '网站列表', url: '/websites'},
|
||||||
@@ -52,16 +64,37 @@ export const openflareWebsiteSubNav = [
|
|||||||
|
|
||||||
const nonConsoleRoutePrefixes = ['/admin', '/settings', '/files', '/home', '/login', '/register', '/docs'];
|
const nonConsoleRoutePrefixes = ['/admin', '/settings', '/files', '/home', '/login', '/register', '/docs'];
|
||||||
|
|
||||||
|
export function matchesNavPath(
|
||||||
|
pathname: string,
|
||||||
|
url: string,
|
||||||
|
childUrls?: string[],
|
||||||
|
): boolean {
|
||||||
|
if (url === '/') {
|
||||||
|
return pathname === '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathname === url || pathname.startsWith(`${url}/`)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (childUrls ?? []).some(
|
||||||
|
(childUrl) => pathname === childUrl || pathname.startsWith(`${childUrl}/`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNavGroupActive(pathname: string, group: OpenFlareNavGroup): boolean {
|
||||||
|
return group.items.some((item) => matchesNavPath(pathname, item.url, item.childUrls));
|
||||||
|
}
|
||||||
|
|
||||||
/** 判断当前路径是否属于 OpenFlare 业务控制台(用于顶栏版本入口等) */
|
/** 判断当前路径是否属于 OpenFlare 业务控制台(用于顶栏版本入口等) */
|
||||||
export function isOpenFlareConsoleRoute(pathname: string): boolean {
|
export function isOpenFlareConsoleRoute(pathname: string): boolean {
|
||||||
if (nonConsoleRoutePrefixes.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`))) {
|
if (nonConsoleRoutePrefixes.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return openflareNavItems.some((item) => {
|
if (isNavGroupActive(pathname, openflareWebsiteNavGroup)) {
|
||||||
if (item.url === '/') {
|
return true;
|
||||||
return pathname === '/';
|
}
|
||||||
}
|
|
||||||
return pathname === item.url || pathname.startsWith(`${item.url}/`);
|
return openflareNavItems.some((item) => matchesNavPath(pathname, item.url, item.childUrls));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ sidebar: false
|
|||||||
- 补全 Wavelet 边缘节点详情数据看板:迁移运行诊断摘要、系统信息、实时资源、网络流量、24 小时请求/容量/网络/磁盘趋势、请求结构分布与健康事件时间线。
|
- 补全 Wavelet 边缘节点详情数据看板:迁移运行诊断摘要、系统信息、实时资源、网络流量、24 小时请求/容量/网络/磁盘趋势、请求结构分布与健康事件时间线。
|
||||||
- 节点详情页改为 Tabs 布局(概览 / 数据看板 / 配置与部署),统一 Edge/Relay/Tunnel 详情壳层并支持 `tab` URL 参数深链。
|
- 节点详情页改为 Tabs 布局(概览 / 数据看板 / 配置与部署),统一 Edge/Relay/Tunnel 详情壳层并支持 `tab` URL 参数深链。
|
||||||
- 节点详情 24 小时网络/磁盘 IO 趋势图对齐原版 ECharts 样式,恢复系列摘要卡与平滑面积折线图。
|
- 节点详情 24 小时网络/磁盘 IO 趋势图对齐原版 ECharts 样式,恢复系列摘要卡与平滑面积折线图。
|
||||||
|
- 侧栏新增「网站管理」折叠组(shadcn Collapsible + SidebarMenuSub),收纳网站、证书、DNS、源站入口。
|
||||||
- OpenFlare 前端路由去除 `/openflare` 前缀,业务页面直接挂载于 `/`(如 `/nodes`、`/websites`);侧栏移除「首页」「我的文件」;旧路径 `/openflare/*`、`/home` 永久重定向至新路径。
|
- OpenFlare 前端路由去除 `/openflare` 前缀,业务页面直接挂载于 `/`(如 `/nodes`、`/websites`);侧栏移除「首页」「我的文件」;旧路径 `/openflare/*`、`/home` 永久重定向至新路径。
|
||||||
|
|
||||||
## [v2.3.4] - 2026-06-17
|
## [v2.3.4] - 2026-06-17
|
||||||
|
|||||||
Reference in New Issue
Block a user