✨ feat: add connections page, rename dashboard page to overview
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import type { NavTab } from '@/components/layout/app-sidebar'
|
||||
|
||||
const VALID_TABS: NavTab[] = [
|
||||
'overview',
|
||||
'connections',
|
||||
'routes',
|
||||
'topology',
|
||||
'interfaces',
|
||||
'settings',
|
||||
]
|
||||
|
||||
function getTabFromHash(hash: string): NavTab {
|
||||
const cleanHash = hash.replace(/^#\/?/, '').trim().toLowerCase()
|
||||
if (!cleanHash || cleanHash === '') {
|
||||
return 'overview'
|
||||
}
|
||||
// 向后兼容旧的 dashboard hash
|
||||
if (cleanHash === 'dashboard') {
|
||||
return 'overview'
|
||||
}
|
||||
const matchingTab = VALID_TABS.find((tab) => tab === cleanHash)
|
||||
return matchingTab || 'overview'
|
||||
}
|
||||
|
||||
export function useHashRoute() {
|
||||
const [currentTab, setCurrentTabState] = useState<NavTab>(() =>
|
||||
getTabFromHash(window.location.hash)
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const handleHashChange = () => {
|
||||
const newTab = getTabFromHash(window.location.hash)
|
||||
setCurrentTabState(newTab)
|
||||
}
|
||||
|
||||
// If current hash is empty, initialize with #/overview
|
||||
if (!window.location.hash || window.location.hash === '#' || window.location.hash === '#/') {
|
||||
window.location.hash = '#/overview'
|
||||
} else if (window.location.hash === '#/dashboard') {
|
||||
window.location.hash = '#/overview'
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', handleHashChange)
|
||||
return () => {
|
||||
window.removeEventListener('hashchange', handleHashChange)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const navigate = useCallback((tab: NavTab) => {
|
||||
window.location.hash = `#/${tab}`
|
||||
setCurrentTabState(tab)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
currentTab,
|
||||
navigate,
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,12 @@ interface UseKlalbSSEResult {
|
||||
error: string | null
|
||||
reconnectAll: () => Promise<boolean>
|
||||
refreshStatus: () => Promise<void>
|
||||
addLink: (address: string) => Promise<{ success: boolean; message?: string }>
|
||||
removeLink: (address: string) => Promise<{ success: boolean; message?: string }>
|
||||
executeLinkAction: (
|
||||
address: string,
|
||||
action: 'reconnect' | 'disconnect' | 'remove'
|
||||
) => Promise<{ success: boolean; message?: string }>
|
||||
}
|
||||
|
||||
export function useKlalbSSE(): UseKlalbSSEResult {
|
||||
@@ -20,14 +26,21 @@ export function useKlalbSSE(): UseKlalbSSEResult {
|
||||
|
||||
const refreshStatus = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch('/api/status')
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as SystemStatus
|
||||
const [statusRes, linksRes] = await Promise.all([
|
||||
fetch('/api/status'),
|
||||
fetch('/api/links'),
|
||||
])
|
||||
if (statusRes.ok) {
|
||||
const data = (await statusRes.json()) as SystemStatus
|
||||
setStatus(data)
|
||||
setError(null)
|
||||
}
|
||||
if (linksRes.ok) {
|
||||
const linksData = await linksRes.json()
|
||||
setLinks(Array.isArray(linksData) ? linksData : [])
|
||||
}
|
||||
setError(null)
|
||||
} catch (err) {
|
||||
console.warn('Failed to fetch status fallback:', err)
|
||||
console.warn('Failed to fetch status/links fallback:', err)
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -41,6 +54,74 @@ export function useKlalbSSE(): UseKlalbSSEResult {
|
||||
}
|
||||
}, [])
|
||||
|
||||
const addLink = useCallback(
|
||||
async (address: string): Promise<{ success: boolean; message?: string }> => {
|
||||
try {
|
||||
const res = await fetch('/api/links', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ address: address.trim() }),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (res.ok && data.success) {
|
||||
refreshStatus()
|
||||
return { success: true }
|
||||
}
|
||||
return { success: false, message: data.error || '添加连接失败' }
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
return { success: false, message: msg }
|
||||
}
|
||||
},
|
||||
[refreshStatus]
|
||||
)
|
||||
|
||||
const removeLink = useCallback(
|
||||
async (address: string): Promise<{ success: boolean; message?: string }> => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/links?address=${encodeURIComponent(address.trim())}`,
|
||||
{ method: 'DELETE' }
|
||||
)
|
||||
const data = await res.json()
|
||||
if (res.ok && data.success) {
|
||||
refreshStatus()
|
||||
return { success: true }
|
||||
}
|
||||
return { success: false, message: data.error || '移除连接失败' }
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
return { success: false, message: msg }
|
||||
}
|
||||
},
|
||||
[refreshStatus]
|
||||
)
|
||||
|
||||
const executeLinkAction = useCallback(
|
||||
async (
|
||||
address: string,
|
||||
action: 'reconnect' | 'disconnect' | 'remove'
|
||||
): Promise<{ success: boolean; message?: string }> => {
|
||||
try {
|
||||
const res = await fetch('/api/links/action', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ address, action }),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (res.ok && data.success) {
|
||||
refreshStatus()
|
||||
return { success: true }
|
||||
}
|
||||
return { success: false, message: data.error || `执行 ${action} 失败` }
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
return { success: false, message: msg }
|
||||
}
|
||||
},
|
||||
[refreshStatus]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
let isUnmounted = false
|
||||
|
||||
@@ -65,7 +146,7 @@ export function useKlalbSSE(): UseKlalbSSEResult {
|
||||
if (data.status) {
|
||||
setStatus(data.status)
|
||||
}
|
||||
if (data.links) {
|
||||
if (Array.isArray(data.links)) {
|
||||
setLinks(data.links)
|
||||
}
|
||||
setIsConnected(true)
|
||||
@@ -124,5 +205,8 @@ export function useKlalbSSE(): UseKlalbSSEResult {
|
||||
error,
|
||||
reconnectAll,
|
||||
refreshStatus,
|
||||
addLink,
|
||||
removeLink,
|
||||
executeLinkAction,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user