更新侧边栏改造使用路由结构,更新中间件重定向

This commit is contained in:
wmp
2025-10-14 16:54:55 +08:00
parent 10395a49c1
commit 010990ea3c
14 changed files with 117 additions and 278 deletions

View File

@@ -41,7 +41,7 @@ export default function LoginPage() {
}) })
setAuth(true) setAuth(true)
await new Promise(resolve => setTimeout(resolve, 1000)) await new Promise(resolve => setTimeout(resolve, 1000))
router.push('/dashboard') router.push('/gatewayinfo')
router.refresh() router.refresh()
} }
else { else {

View File

@@ -139,7 +139,7 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool
if (error) return <ErrorCard title="节点分配状态" error={error} onRetry={fetchData} /> if (error) return <ErrorCard title="节点分配状态" error={error} onRetry={fetchData} />
return ( return (
<div className="flex flex-col bg-white shadow p-6 overflow-hidden"> <div className="flex flex-col w-full bg-white shadow p-6 overflow-hidden">
<h2 className="text-lg font-semibold mb-4"></h2> <h2 className="text-lg font-semibold mb-4"></h2>
<div className="mb-4 flex flex-wrap items-center gap-3"> <div className="mb-4 flex flex-wrap items-center gap-3">

View File

@@ -30,7 +30,7 @@ export default function CityNodeStats() {
if (loading) { if (loading) {
return ( return (
<div className="bg-white p-6 overflow-hidden"> <div className="bg-white p-6 w-full overflow-hidden">
<h2 className="text-lg font-semibold mb-4"></h2> <h2 className="text-lg font-semibold mb-4"></h2>
<div className="text-gray-600">...</div> <div className="text-gray-600">...</div>
</div> </div>
@@ -38,7 +38,7 @@ export default function CityNodeStats() {
} }
return ( return (
<div className="flex flex-col bg-white p-6 overflow-hidden"> <div className="flex flex-col w-full bg-white p-6 overflow-hidden">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-semibold"></h2> <h2 className="text-lg font-semibold"></h2>
<span className="text-sm text-gray-500"> <span className="text-sm text-gray-500">

View File

@@ -166,14 +166,14 @@ export default function Edge() {
}, []) }, [])
if (loading) return ( if (loading) return (
<div className="bg-white shadow p-6"> <div className="bg-white w-full shadow p-6">
<h2 className="text-xl font-semibold text-gray-800 mb-4"></h2> <h2 className="text-xl font-semibold text-gray-800 mb-4"></h2>
<div className="text-center py-8">...</div> <div className="text-center py-8">...</div>
</div> </div>
) )
if (error) return ( if (error) return (
<div className="bg-white shadow p-6"> <div className="bg-white w-full shadow p-6">
<h2 className="text-xl font-semibold text-gray-800 mb-4"></h2> <h2 className="text-xl font-semibold text-gray-800 mb-4"></h2>
<div className="text-center py-8 text-red-600">{error}</div> <div className="text-center py-8 text-red-600">{error}</div>
<button <button
@@ -250,7 +250,7 @@ export default function Edge() {
</div> </div>
{data.length === 0 ? ( {data.length === 0 ? (
<div className="text-center py-12"> <div className="text-center w-full py-12">
<div className="text-gray-400 text-4xl mb-4">📋</div> <div className="text-gray-400 text-4xl mb-4">📋</div>
<p className="text-gray-600"></p> <p className="text-gray-600"></p>
</div> </div>

View File

@@ -144,7 +144,7 @@ export default function Gatewayinfo() {
if (loading) { if (loading) {
return ( return (
<div className="bg-white shadow p-6 overflow-hidden"> <div className="bg-white w-full shadow p-6 overflow-hidden">
<h2 className="text-lg font-semibold mb-4"></h2> <h2 className="text-lg font-semibold mb-4"></h2>
<div className="text-center py-8">...</div> <div className="text-center py-8">...</div>
</div> </div>
@@ -153,7 +153,7 @@ export default function Gatewayinfo() {
if (error) { if (error) {
return ( return (
<div className="bg-white shadow p-6"> <div className="bg-white w-full shadow p-6">
<h2 className="text-lg font-semibold mb-4"></h2> <h2 className="text-lg font-semibold mb-4"></h2>
<div className="text-center py-8 text-red-600">{error}</div> <div className="text-center py-8 text-red-600">{error}</div>
</div> </div>
@@ -161,7 +161,7 @@ export default function Gatewayinfo() {
} }
return ( return (
<div className="flex flex-col bg-white p-6 overflow-hidden"> <div className="flex flex-col w-full bg-white p-6 overflow-hidden">
<div className="flex gap-6"> <div className="flex gap-6">
<div className="flex flex-3 justify-between "> <div className="flex flex-3 justify-between ">
<span className="text-lg pt-2 font-semibold mb-4"></span> <span className="text-lg pt-2 font-semibold mb-4"></span>
@@ -220,14 +220,7 @@ export default function Gatewayinfo() {
> >
<TableCell className="px-4 py-2"> <TableCell className="px-4 py-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* <button
onClick={() => {
router.push(`/dashboard?tab=gateway&mac=${item.macaddr}`)
}}
className="font-mono text-blue-600 hover:text-blue-800 hover:underline cursor-pointer"
> */}
{item.macaddr} {item.macaddr}
{/* </button> */}
<SmartCopyButton data={item.macaddr} /> <SmartCopyButton data={item.macaddr} />
</div> </div>
</TableCell> </TableCell>

103
src/app/(root)/layout.tsx Normal file
View File

@@ -0,0 +1,103 @@
'use client'
import { ReactNode, useState } from 'react'
import { useRouter, usePathname } from 'next/navigation'
import { logout } from '@/actions/auth'
import { LogOut } from 'lucide-react'
import Link from 'next/link'
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const [isLoading, setIsLoading] = useState(false)
const router = useRouter()
const pathname = usePathname()
const handleLogout = async () => {
setIsLoading(true)
try {
const response = await logout()
if (response) {
router.push('/login')
router.refresh()
}
}
catch (error) {
console.error('退出错误:', error)
}
finally {
setIsLoading(false)
}
}
const isActive = (path: string) => {
return pathname === path
}
return (
<div className="bg-gray-100 w-screen h-screen flex flex-col overflow-hidden">
{/* 顶部导航栏 */}
<nav className="bg-white flex-none border-b h-16 shadow-sm">
<div className="px-4 sm:px-6">
<div className="flex justify-between h-16 items-center">
<div className="flex items-center">
<h1 className="text-xl font-bold text-gray-900"></h1>
</div>
{/* 简化的退出按钮 */}
<button
onClick={handleLogout}
disabled={isLoading}
className="flex items-center space-x-2 px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 disabled:opacity-50 transition-colors"
>
<LogOut className="h-4 w-4" />
<span>{isLoading ? '退出中...' : '退出登录'}</span>
</button>
</div>
</div>
</nav>
{/* 主要内容区域 */}
<div className="flex flex-1 overflow-hidden">
{/* 侧边栏 */}
<div className="w-64 bg-gray-100 border-r border-gray-200 flex flex-col">
<nav className="flex-1 p-4 space-y-2">
<NavbarItem href="/gatewayinfo" active={isActive('/gatewayinfo')}></NavbarItem>
<NavbarItem href="/gatewayConfig" active={isActive('/gatewayConfig')}></NavbarItem>
<NavbarItem href="/gatewayMonitor" active={isActive('/gatewayMonitor')}></NavbarItem>
<NavbarItem href="/cityNodeStats" active={isActive('/cityNodeStats')}></NavbarItem>
<NavbarItem href="/allocationStatus" active={isActive('/allocationStatus')}></NavbarItem>
<NavbarItem href="/edge" active={isActive('/edge')}></NavbarItem>
<NavbarItem href="/settings" active={isActive('/settings')}></NavbarItem>
</nav>
</div>
{/* 内容区域 */}
<div className="flex flex-3 w-full overflow-hidden">
{children}
</div>
</div>
</div>
)
}
function NavbarItem(props: {
href: string
active: boolean
children: ReactNode
}) {
return (
<Link
href={props.href}
className={`block px-3 py-2 rounded-md text-sm font-medium transition-colors ${
props.active
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
{props.children}
</Link>
)
}

View File

@@ -123,8 +123,8 @@ export default function Settings() {
) )
return ( return (
<div className="bg-white p-4 md:p-8"> <div className="bg-white p-6 flex w-full">
<div className="max-w-6xl mx-auto"> <div className="flex flex-col w-full">
<div className="flex justify-between items-center mb-6"> <div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold"></h1> <h1 className="text-3xl font-bold"></h1>
<Button onClick={() => setIsCreateMode(!isCreateMode)}> <Button onClick={() => setIsCreateMode(!isCreateMode)}>

View File

@@ -1,138 +0,0 @@
'use client'
import { useState, useEffect, Suspense } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import Gatewayinfo from './components/gatewayinfo'
import GatewayConfig from './components/gatewayConfig'
import GatewayConfigs from './components/gatewayConfigs'
import CityNodeStats from './components/cityNodeStats'
import AllocationStatus from './components/allocationStatus'
import Settings from './components/settings'
import Edge from './components/edge'
import { LogOut } from 'lucide-react'
import { logout } from '@/actions/auth'
const tabs = [
{ id: 'gatewayInfo', label: '网关信息' },
{ id: 'gateway', label: '网关配置' },
{ id: 'gateways', label: '网关查询' },
{ id: 'city', label: '城市信息' },
{ id: 'allocation', label: '分配状态' },
{ id: 'edge', label: '节点信息' },
{ id: 'setting', label: '设置' },
]
function DashboardContent() {
const [activeTab, setActiveTab] = useState('gatewayInfo')
const [isLoading, setIsLoading] = useState(false)
const router = useRouter()
const searchParams = useSearchParams()
// 监听URL参数变化
useEffect(() => {
const urlTab = searchParams.get('tab')
if (urlTab && tabs.some(tab => tab.id === urlTab)) {
setActiveTab(urlTab)
}
}, [searchParams])
// 退出登录
const handleLogout = async () => {
setIsLoading(true)
try {
const response = await logout()
if (response) {
// 退出成功后跳转到登录页
router.push('/login')
router.refresh()
}
else {
console.error('退出失败')
}
}
catch (error) {
console.error('退出错误:', error)
}
finally {
setIsLoading(false)
}
}
const handleTabClick = (tabId: string) => {
setActiveTab(tabId)
// 更新 URL 参数
const params = new URLSearchParams()
params.set('tab', tabId)
router.push(`/dashboard?${params.toString()}`)
}
return (
<div className=" bg-gray-100 w-screen h-screen flex flex-col">
<nav className="bg-white flex-none h-16 shadow-sm">
<div className="px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16 items-center">
<div className="flex items-center">
<h1 className="text-xl font-bold text-gray-900"></h1>
</div>
{/* 简化的退出按钮 */}
<button
onClick={handleLogout}
disabled={isLoading}
className="flex items-center space-x-2 px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 disabled:opacity-50 transition-colors"
>
<LogOut className="h-4 w-4" />
<span>{isLoading ? '退出中...' : '退出登录'}</span>
</button>
</div>
</div>
</nav>
<div className="flex flex-3 overflow-hidden">
<div className="border-b border-gray-200 mb-6">
<nav className="flex flex-col w-64 -mb-px space-x-8">
{tabs.map(tab => (
<button
key={tab.id}
onClick={() => handleTabClick(tab.id)}
className={`py-2 px-1 h-12 border-b-2 font-medium text-sm ${
activeTab === tab.id
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
{tab.label}
</button>
))}
</nav>
</div>
<div className="grid grid-cols-1 gap-6 border flex-auto">
{activeTab === 'gatewayInfo' && <Gatewayinfo />}
{activeTab === 'gateway' && <GatewayConfig />}
{activeTab === 'gateways' && <GatewayConfigs />}
{activeTab === 'city' && <CityNodeStats />}
{activeTab === 'allocation' && <AllocationStatus detailed />}
{activeTab === 'edge' && <Edge />}
{activeTab === 'setting' && <Settings />}
</div>
</div>
</div>
)
}
export default function Dashboard() {
return (
<Suspense fallback={(
<div className=" bg-gray-100 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto"></div>
<p className="mt-4 text-gray-600">...</p>
</div>
</div>
)}>
<DashboardContent />
</Suspense>
)
}

View File

@@ -1,108 +0,0 @@
'use client'
import { findConfigs } from '@/actions/config'
import { gatewayConfigGet } from '@/actions/remote'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { useState } from 'react'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
type EdgeConfig = {
port?: string
edge?: string
city?: string
_index: number
}
export default function DebugConfigPage() {
const [macaddr, setMacaddr] = useState('')
const [remotes, setRemotes] = useState<EdgeConfig[]>([])
const [locals, setLocals] = useState<EdgeConfig[]>([])
const fetch = async (macaddr: string) => {
try {
console.log('fetch', macaddr)
if (!macaddr) return
const rawLocal = await findConfigs({ macaddr })
console.log('raw local', rawLocal)
const rawRemote = await gatewayConfigGet({ macaddr })
console.log('raw remote', rawRemote)
setLocals(rawLocal.map(rule => ({
port: rule.network,
edge: rule.edge,
city: rule.cityhash,
_index: parseInt(rule.network.split('.')[3] || '0'),
})).sort((a, b) => a._index - b._index))
setRemotes(rawRemote.rules.map((rule) => {
const port = rule.network.find(n => !!n)
return ({
port: port,
edge: rule.edge.find(n => !!n),
city: rule.cityhash,
_index: port ? parseInt(port.split('.')[3]) : 0,
})
}).sort((a, b) => a._index - b._index))
}
catch (e) {
console.error('数据获取失败', e)
}
}
return (
<div className="flex-auto overflow-hidden flex flex-col p-6 gap-4.5">
<div className="flex-none flex gap-3">
<Input type="text" name="macaddr" value={macaddr} onChange={e => setMacaddr(e.target.value)} className="flex-none basis-60" />
<Button onClick={() => fetch(macaddr)}></Button>
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{!locals.length || !remotes.length ? (
<TableRow>
<TableCell colSpan={3} className="text-center"></TableCell>
</TableRow>
) : locals.length !== remotes.length ? (
<TableRow>
<TableCell colSpan={3} className="text-center"></TableCell>
</TableRow>
) : (
locals.map((item, index) => (
<TableRow key={index}>
<TableCell>
{item.port === remotes[index].port ? (
<span className="text-green-500">{item.port}</span>
) : (
<span className="text-red-500">{item.port} : {remotes[index].port}</span>
)}
</TableCell>
<TableCell>
{item.edge === remotes[index].edge ? (
<span className="text-green-500">{item.edge}</span>
) : (
<span className="text-red-500">{item.edge} : {remotes[index].edge}</span>
)}
</TableCell>
<TableCell>
{item.city === remotes[index].city ? (
<span className="text-green-500">{item.city}</span>
) : (
<span className="text-red-500">{item.city} : {remotes[index].city}</span>
)}
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
)
}

View File

@@ -1,11 +0,0 @@
import { ReactNode } from 'react'
export default async function DebugLayout(props: {
children: ReactNode
}) {
return (
<div className="w-screen h-screen flex flex-col">
{props.children}
</div>
)
}

View File

@@ -1,6 +1,6 @@
export default function LoadingCard({ title }: { title: string }) { export default function LoadingCard({ title }: { title: string }) {
return ( return (
<div className="bg-white shadow p-6"> <div className="bg-white w-full shadow p-6">
<div className="animate-pulse"> <div className="animate-pulse">
<div className="h-6 bg-gray-200 rounded w-1/4 mb-4"></div> <div className="h-6 bg-gray-200 rounded w-1/4 mb-4"></div>
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-3 gap-4">

View File

@@ -26,7 +26,7 @@ export async function middleware(request: NextRequest) {
// 给没有页面的路径添加跳转页面 // 给没有页面的路径添加跳转页面
if (request.nextUrl.pathname === '/') { if (request.nextUrl.pathname === '/') {
return NextResponse.redirect(new URL('/dashboard', request.url)) return NextResponse.redirect(new URL('/gatewayinfo', request.url))
} }
return NextResponse.next() return NextResponse.next()