更新侧边栏改造使用路由结构,更新中间件重定向
This commit is contained in:
@@ -41,7 +41,7 @@ export default function LoginPage() {
|
||||
})
|
||||
setAuth(true)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
router.push('/dashboard')
|
||||
router.push('/gatewayinfo')
|
||||
router.refresh()
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -139,7 +139,7 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool
|
||||
if (error) return <ErrorCard title="节点分配状态" error={error} onRetry={fetchData} />
|
||||
|
||||
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>
|
||||
|
||||
<div className="mb-4 flex flex-wrap items-center gap-3">
|
||||
@@ -30,7 +30,7 @@ export default function CityNodeStats() {
|
||||
|
||||
if (loading) {
|
||||
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>
|
||||
<div className="text-gray-600">加载中...</div>
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@ export default function CityNodeStats() {
|
||||
}
|
||||
|
||||
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">
|
||||
<h2 className="text-lg font-semibold">城市节点数量分布</h2>
|
||||
<span className="text-sm text-gray-500">
|
||||
@@ -166,14 +166,14 @@ export default function Edge() {
|
||||
}, [])
|
||||
|
||||
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>
|
||||
<div className="text-center py-8">加载节点数据中...</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
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>
|
||||
<div className="text-center py-8 text-red-600">{error}</div>
|
||||
<button
|
||||
@@ -250,7 +250,7 @@ export default function Edge() {
|
||||
</div>
|
||||
|
||||
{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>
|
||||
<p className="text-gray-600">暂无节点数据</p>
|
||||
</div>
|
||||
@@ -144,7 +144,7 @@ export default function Gatewayinfo() {
|
||||
|
||||
if (loading) {
|
||||
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>
|
||||
<div className="text-center py-8">加载网关信息中...</div>
|
||||
</div>
|
||||
@@ -153,7 +153,7 @@ export default function Gatewayinfo() {
|
||||
|
||||
if (error) {
|
||||
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>
|
||||
<div className="text-center py-8 text-red-600">{error}</div>
|
||||
</div>
|
||||
@@ -161,7 +161,7 @@ export default function Gatewayinfo() {
|
||||
}
|
||||
|
||||
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 flex-3 justify-between ">
|
||||
<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">
|
||||
<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}
|
||||
{/* </button> */}
|
||||
<SmartCopyButton data={item.macaddr} />
|
||||
</div>
|
||||
</TableCell>
|
||||
103
src/app/(root)/layout.tsx
Normal file
103
src/app/(root)/layout.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -123,8 +123,8 @@ export default function Settings() {
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="bg-white p-4 md:p-8">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="bg-white p-6 flex w-full">
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-3xl font-bold">用户管理</h1>
|
||||
<Button onClick={() => setIsCreateMode(!isCreateMode)}>
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
export default function LoadingCard({ title }: { title: string }) {
|
||||
return (
|
||||
<div className="bg-white shadow p-6">
|
||||
<div className="bg-white w-full shadow p-6">
|
||||
<div className="animate-pulse">
|
||||
<div className="h-6 bg-gray-200 rounded w-1/4 mb-4"></div>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
|
||||
@@ -26,7 +26,7 @@ export async function middleware(request: NextRequest) {
|
||||
|
||||
// 给没有页面的路径添加跳转页面
|
||||
if (request.nextUrl.pathname === '/') {
|
||||
return NextResponse.redirect(new URL('/dashboard', request.url))
|
||||
return NextResponse.redirect(new URL('/gatewayinfo', request.url))
|
||||
}
|
||||
|
||||
return NextResponse.next()
|
||||
|
||||
Reference in New Issue
Block a user