Skip to content

Commit 175fcd2

Browse files
committed
feat: new tunnel details info layout
1 parent a209805 commit 175fcd2

File tree

107 files changed

+13708
-9348
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+13708
-9348
lines changed

web/src/App.tsx

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Route, Routes } from "react-router-dom";
2+
23
import DefaultLayout from "./layouts/default";
34

45
// 页面组件
@@ -17,27 +18,32 @@ import DebugPage from "./pages/debug";
1718
import EndpointDetailsPage from "./pages/endpoints/details";
1819
import EndpointSSEDebugPage from "./pages/endpoints/sse-debug";
1920
import ExamplesPage from "./pages/examples";
21+
import IconComparisonPage from "./pages/icon-comparison";
2022

2123
function App() {
2224
return (
2325
<DefaultLayout>
2426
<Routes>
25-
<Route path="/login" element={<LoginPage />} />
26-
<Route path="/oauth-error" element={<OAuthErrorPage />} />
27-
<Route path="/setup-guide" element={<SetupGuidePage />} />
28-
<Route path="/dashboard" element={<DashboardPage />} />
29-
<Route path="/tunnels" element={<TunnelsPage />} />
30-
<Route path="/tunnels/create" element={<TunnelCreatePage />} />
31-
<Route path="/tunnels/details" element={<TunnelDetailsPage />} />
32-
<Route path="/templates" element={<TemplatesPage />} />
33-
<Route path="/endpoints" element={<EndpointsPage />} />
34-
<Route path="/endpoints/details" element={<EndpointDetailsPage />} />
35-
<Route path="/endpoints/sse-debug" element={<EndpointSSEDebugPage />} />
36-
<Route path="/settings" element={<SettingsPage />} />
37-
<Route path="/settings/version-history" element={<VersionHistoryPage />} />
38-
<Route path="/docs" element={<ExamplesPage />} />
39-
<Route path="/debug" element={<DebugPage />} />
40-
<Route path="/" element={<DashboardPage />} />
27+
<Route element={<LoginPage />} path="/login" />
28+
<Route element={<OAuthErrorPage />} path="/oauth-error" />
29+
<Route element={<SetupGuidePage />} path="/setup-guide" />
30+
<Route element={<DashboardPage />} path="/dashboard" />
31+
<Route element={<TunnelsPage />} path="/tunnels" />
32+
<Route element={<TunnelCreatePage />} path="/tunnels/create" />
33+
<Route element={<TunnelDetailsPage />} path="/tunnels/details" />
34+
<Route element={<TemplatesPage />} path="/templates" />
35+
<Route element={<EndpointsPage />} path="/endpoints" />
36+
<Route element={<EndpointDetailsPage />} path="/endpoints/details" />
37+
<Route element={<EndpointSSEDebugPage />} path="/endpoints/sse-debug" />
38+
<Route element={<SettingsPage />} path="/settings" />
39+
<Route
40+
element={<VersionHistoryPage />}
41+
path="/settings/version-history"
42+
/>
43+
<Route element={<ExamplesPage />} path="/docs" />
44+
<Route element={<DebugPage />} path="/debug" />
45+
<Route element={<IconComparisonPage />} path="/icon-comparison" />
46+
<Route element={<DashboardPage />} path="/" />
4147
</Routes>
4248
</DefaultLayout>
4349
);
Lines changed: 69 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
2-
import { useNavigate } from 'react-router-dom';
3-
import { buildApiUrl } from '@/lib/utils';
1+
import {
2+
createContext,
3+
useContext,
4+
useEffect,
5+
useState,
6+
ReactNode,
7+
} from "react";
8+
import { useNavigate } from "react-router-dom";
9+
10+
import { buildApiUrl } from "@/lib/utils";
411

512
interface User {
613
username: string;
@@ -18,9 +25,11 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined);
1825

1926
export function useAuth() {
2027
const context = useContext(AuthContext);
28+
2129
if (context === undefined) {
22-
throw new Error('useAuth 必须在 AuthProvider 内部使用');
30+
throw new Error("useAuth 必须在 AuthProvider 内部使用");
2331
}
32+
2433
return context;
2534
}
2635

@@ -36,54 +45,66 @@ export function AuthProvider({ children }: AuthProviderProps) {
3645

3746
// 初始挂载时,尝试从 localStorage 读取用户信息,提供"乐观"登录体验,防止刷新立刻跳登录页
3847
useEffect(() => {
39-
if (typeof window !== 'undefined') {
48+
if (typeof window !== "undefined") {
4049
try {
41-
const stored = localStorage.getItem('nodepass.user');
50+
const stored = localStorage.getItem("nodepass.user");
51+
4252
if (stored) {
4353
const storedUser = JSON.parse(stored) as User;
54+
4455
setUser(storedUser);
45-
console.log('📦 从localStorage恢复用户状态', storedUser);
56+
console.log("📦 从localStorage恢复用户状态", storedUser);
4657
}
4758
} catch (e) {
48-
console.error('读取本地用户失败', e);
49-
localStorage.removeItem('nodepass.user');
59+
console.error("读取本地用户失败", e);
60+
localStorage.removeItem("nodepass.user");
5061
}
5162
}
5263
}, []);
5364

5465
// 验证当前用户会话
5566
const checkAuth = async (forceCheck = false) => {
56-
console.log('🔍 开始检查身份验证状态', { forceCheck, user: user?.username, loading });
57-
67+
console.log("🔍 开始检查身份验证状态", {
68+
forceCheck,
69+
user: user?.username,
70+
loading,
71+
});
72+
5873
// 如果正在加载中且不是强制检查,则跳过
5974
if (loading && !forceCheck) {
60-
console.log('⚡ 跳过身份验证检查(正在加载中)');
75+
console.log("⚡ 跳过身份验证检查(正在加载中)");
76+
6177
return;
6278
}
63-
79+
6480
// 避免频繁检查,30秒内不重复检查(除非强制检查)
6581
const now = Date.now();
82+
6683
if (!forceCheck && now - lastCheckTime < 30000) {
67-
console.log('⏭️ 跳过身份验证检查(30秒内已检查)');
84+
console.log("⏭️ 跳过身份验证检查(30秒内已检查)");
6885
setLoading(false);
86+
6987
return;
7088
}
71-
89+
7290
setLoading(true);
73-
91+
7492
try {
75-
const response = await fetch(buildApiUrl('/api/auth/me'));
76-
console.log('🔍 身份验证检查响应', {
77-
status: response.status,
78-
ok: response.ok
93+
const response = await fetch(buildApiUrl("/api/auth/me"));
94+
95+
console.log("🔍 身份验证检查响应", {
96+
status: response.status,
97+
ok: response.ok,
7998
});
80-
99+
81100
if (response.ok) {
82101
const userData = await response.json();
83-
console.log('✅ 身份验证成功', userData);
102+
103+
console.log("✅ 身份验证成功", userData);
84104

85105
// 兼容两种返回格式:{ user: { username: "" } } 或 { username: "" }
86106
let extractedUser: User | null = null;
107+
87108
if (userData.user && userData.user.username) {
88109
extractedUser = userData.user as User;
89110
} else if (userData.username) {
@@ -94,27 +115,30 @@ export function AuthProvider({ children }: AuthProviderProps) {
94115
setUser(extractedUser);
95116

96117
// 同步到 localStorage
97-
if (typeof window !== 'undefined') {
98-
localStorage.setItem('nodepass.user', JSON.stringify(extractedUser));
118+
if (typeof window !== "undefined") {
119+
localStorage.setItem(
120+
"nodepass.user",
121+
JSON.stringify(extractedUser),
122+
);
99123
}
100124
} else {
101125
// 格式异常视为未登录
102126
setUser(null);
103-
if (typeof window !== 'undefined') {
104-
localStorage.removeItem('nodepass.user');
127+
if (typeof window !== "undefined") {
128+
localStorage.removeItem("nodepass.user");
105129
}
106130
}
107131
} else {
108-
console.log('❌ 身份验证失败,清除用户状态');
132+
console.log("❌ 身份验证失败,清除用户状态");
109133
setUser(null);
110134

111-
if (typeof window !== 'undefined') {
112-
localStorage.removeItem('nodepass.user');
135+
if (typeof window !== "undefined") {
136+
localStorage.removeItem("nodepass.user");
113137
}
114138
}
115139
setLastCheckTime(now);
116140
} catch (error) {
117-
console.error('🚨 验证身份失败:', error);
141+
console.error("🚨 验证身份失败:", error);
118142
setUser(null);
119143
} finally {
120144
setLoading(false);
@@ -123,27 +147,27 @@ export function AuthProvider({ children }: AuthProviderProps) {
123147

124148
// 登出函数
125149
const logout = async () => {
126-
console.log('👋 开始登出流程');
150+
console.log("👋 开始登出流程");
127151
setLoading(true);
128-
152+
129153
try {
130-
await fetch(buildApiUrl('/api/auth/logout'), {
131-
method: 'POST',
154+
await fetch(buildApiUrl("/api/auth/logout"), {
155+
method: "POST",
132156
});
133-
console.log('✅ 登出请求完成');
157+
console.log("✅ 登出请求完成");
134158
} catch (error) {
135-
console.error('🚨 登出请求失败:', error);
159+
console.error("🚨 登出请求失败:", error);
136160
} finally {
137161
// 清除用户状态和本地存储
138162
setUser(null);
139-
if (typeof window !== 'undefined') {
140-
localStorage.removeItem('nodepass.user');
163+
if (typeof window !== "undefined") {
164+
localStorage.removeItem("nodepass.user");
141165
}
142-
166+
143167
// 延迟跳转,确保状态清理完成
144168
setTimeout(() => {
145169
setLoading(false);
146-
navigate('/login', { replace: true });
170+
navigate("/login", { replace: true });
147171
}, 100);
148172
}
149173
};
@@ -153,13 +177,15 @@ export function AuthProvider({ children }: AuthProviderProps) {
153177
const timeoutId = setTimeout(() => {
154178
checkAuth(true); // 初始检查强制执行
155179
}, 200);
156-
180+
157181
return () => clearTimeout(timeoutId);
158182
}, []);
159183

160184
return (
161-
<AuthContext.Provider value={{ user, loading, logout, checkAuth, setUserDirectly: setUser }}>
185+
<AuthContext.Provider
186+
value={{ user, loading, logout, checkAuth, setUserDirectly: setUser }}
187+
>
162188
{children}
163189
</AuthContext.Provider>
164190
);
165-
}
191+
}

web/src/components/auth/route-guard.tsx

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
1-
import { ReactNode, useEffect, useRef } from 'react';
2-
import { useLocation, useNavigate } from 'react-router-dom';
3-
import { useAuth } from './auth-provider';
1+
import { ReactNode, useEffect, useRef } from "react";
2+
import { useLocation, useNavigate } from "react-router-dom";
3+
4+
import { useAuth } from "./auth-provider";
45

56
interface RouteGuardProps {
67
children: ReactNode;
78
}
89

910
// 公开路由列表(不需要身份验证)
10-
const RAW_PUBLIC_ROUTES = ['/login', '/oauth-error', '/setup-guide'];
11+
const RAW_PUBLIC_ROUTES = ["/login", "/oauth-error", "/setup-guide"];
1112

1213
/**
1314
* 规范化路径,去除末尾斜杠(根路径 `/` 除外)
1415
*/
1516
function normalizePath(path: string): string {
16-
if (path === '/') return path;
17-
return path.replace(/\/+$/, '');
17+
if (path === "/") return path;
18+
19+
return path.replace(/\/+$/, "");
1820
}
1921

2022
const PUBLIC_ROUTES = RAW_PUBLIC_ROUTES.map(normalizePath);
@@ -26,45 +28,51 @@ export function RouteGuard({ children }: RouteGuardProps) {
2628
const navigationTimeoutRef = useRef<NodeJS.Timeout | null>(null);
2729

2830
useEffect(() => {
29-
console.log('🛡️ RouteGuard 状态变化', {
30-
user: user ? `已登录(${user.username})` : '未登录',
31+
console.log("🛡️ RouteGuard 状态变化", {
32+
user: user ? `已登录(${user.username})` : "未登录",
3133
loading,
3234
pathname: location.pathname,
33-
timestamp: new Date().toISOString()
35+
timestamp: new Date().toISOString(),
3436
});
35-
37+
3638
// 清除之前的导航超时
3739
if (navigationTimeoutRef.current) {
3840
clearTimeout(navigationTimeoutRef.current);
3941
navigationTimeoutRef.current = null;
4042
}
41-
43+
4244
if (!loading) {
43-
const isPublicRoute = PUBLIC_ROUTES.includes(normalizePath(location.pathname));
44-
const isSetupGuide = normalizePath(location.pathname) === '/setup-guide';
45-
46-
console.log('🛡️ RouteGuard 路由检查', {
45+
const isPublicRoute = PUBLIC_ROUTES.includes(
46+
normalizePath(location.pathname),
47+
);
48+
const isSetupGuide = normalizePath(location.pathname) === "/setup-guide";
49+
50+
console.log("🛡️ RouteGuard 路由检查", {
4751
pathname: location.pathname,
4852
isPublicRoute,
4953
hasUser: !!user,
5054
user: user,
51-
needsSetup: user ? (user as any).needsSetup : 'no user',
55+
needsSetup: user ? (user as any).needsSetup : "no user",
5256
isSetupGuide,
53-
action: !user && !isPublicRoute ? '重定向到登录页' :
54-
user && isPublicRoute && !isSetupGuide ? '重定向到仪表盘' : '无需重定向'
57+
action:
58+
!user && !isPublicRoute
59+
? "重定向到登录页"
60+
: user && isPublicRoute && !isSetupGuide
61+
? "重定向到仪表盘"
62+
: "无需重定向",
5563
});
56-
64+
5765
// 添加小延迟,避免与其他导航操作冲突
5866
navigationTimeoutRef.current = setTimeout(() => {
5967
if (!user && !isPublicRoute) {
6068
// 用户未登录且访问私有路由,重定向到登录页
61-
console.log('🔒 执行重定向:用户未登录,前往登录页');
62-
navigate('/login', { replace: true });
69+
console.log("🔒 执行重定向:用户未登录,前往登录页");
70+
navigate("/login", { replace: true });
6371
} else if (user && isPublicRoute && !isSetupGuide) {
6472
// 用户已登录但访问公开路由(如登录页),重定向到仪表盘
6573
// 但是允许已登录用户访问引导页面
66-
console.log('👤 执行重定向:用户已登录,前往仪表盘');
67-
navigate('/dashboard', { replace: true });
74+
console.log("👤 执行重定向:用户已登录,前往仪表盘");
75+
navigate("/dashboard", { replace: true });
6876
}
6977
}, 150); // 增加延迟时间,确保登录跳转有足够时间完成
7078
}
@@ -93,9 +101,14 @@ export function RouteGuard({ children }: RouteGuardProps) {
93101
}
94102

95103
// 检查是否应该显示内容
96-
const isPublicRoute = PUBLIC_ROUTES.includes(normalizePath(location.pathname));
97-
const isSetupGuide = normalizePath(location.pathname) === '/setup-guide';
98-
const shouldShowContent = (user && !isPublicRoute) || (!user && isPublicRoute) || (user && isSetupGuide);
104+
const isPublicRoute = PUBLIC_ROUTES.includes(
105+
normalizePath(location.pathname),
106+
);
107+
const isSetupGuide = normalizePath(location.pathname) === "/setup-guide";
108+
const shouldShowContent =
109+
(user && !isPublicRoute) ||
110+
(!user && isPublicRoute) ||
111+
(user && isSetupGuide);
99112

100113
if (!shouldShowContent) {
101114
// 正在重定向中,显示加载状态
@@ -112,4 +125,4 @@ export function RouteGuard({ children }: RouteGuardProps) {
112125
}
113126

114127
return <>{children}</>;
115-
}
128+
}

0 commit comments

Comments
 (0)