Skip to content

Commit 7d7df2a

Browse files
committed
chore: 修复tunnel详情页面poolType显示逻辑
1 parent 31d99fd commit 7d7df2a

File tree

14 files changed

+540
-34
lines changed

14 files changed

+540
-34
lines changed

web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "NodePassDash",
33
"private": true,
4-
"version": "3.3.0",
4+
"version": "3.2.0",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

web/src/components/layout/settings-drawer.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,6 @@ export const SettingsDrawer: React.FC<SettingsDrawerProps> = ({
361361
wrapper: "group-data-[hover=true]:bg-primary-100",
362362
}}
363363
color="primary"
364-
isDisabled={true}
365364
isSelected={settings.autoCheckUpdates}
366365
size="lg"
367366
onValueChange={toggleAutoCheckUpdates}

web/src/lib/hooks/use-nodepass-sse.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect, useRef, useState, useCallback } from "react";
22

3-
import { buildApiUrl } from "@/lib/utils";
3+
import { buildApiUrl, getAuthToken } from "@/lib/utils";
44

55
interface NodePassSSEOptions {
66
onMessage?: (data: any) => void;
@@ -103,6 +103,7 @@ export function useNodePassSSE(
103103
setError(null);
104104

105105
// 使用后端代理接口连接NodePass SSE
106+
const token = getAuthToken();
106107
const proxyUrl = buildApiUrl(
107108
`/api/sse/nodepass-proxy?endpointId=${btoa(
108109
JSON.stringify({
@@ -115,21 +116,12 @@ export function useNodePassSSE(
115116

116117
console.log("[NodePass SSE] 通过代理连接:", proxyUrl);
117118

118-
// 修复URL构建逻辑 - 避免重复的/api路径
119-
let fullUrl;
120-
121-
if (process.env.NODE_ENV === "development") {
122-
// 在开发环境下,buildApiUrl已经返回了正确的路径,不需要再添加apiBase
123-
fullUrl = proxyUrl;
124-
} else {
125-
// 生产环境下确保URL是完整的
126-
fullUrl = proxyUrl.startsWith("http")
127-
? proxyUrl
128-
: `${window.location.origin}${proxyUrl}`;
129-
}
130-
console.log("[NodePass SSE] 完整URL:", fullUrl);
119+
const fullUrl = new URL(proxyUrl, window.location.origin);
120+
if (token) fullUrl.searchParams.set("token", token);
121+
122+
console.log("[NodePass SSE] 完整URL:", fullUrl.toString());
131123

132-
const eventSource = new EventSource(fullUrl);
124+
const eventSource = new EventSource(fullUrl.toString());
133125

134126
// 存储EventSource引用以便清理
135127
eventSourceRef.current = eventSource;

web/src/lib/hooks/use-sse.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect, useRef, useCallback } from "react";
22

3-
import { buildApiUrl } from "@/lib/utils";
3+
import { buildApiUrl, getAuthToken } from "@/lib/utils";
44

55
interface SSEOptions {
66
onMessage?: (event: any) => void;
@@ -36,9 +36,14 @@ export function useTunnelSSE(instanceId: string, options: SSEOptions = {}) {
3636
}
3737

3838
isMountedRef.current = true;
39-
const url = buildApiUrl(`/api/sse/tunnel/${instanceId}`);
39+
const token = getAuthToken();
40+
const url = new URL(
41+
buildApiUrl(`/api/sse/tunnel/${instanceId}`),
42+
window.location.origin,
43+
);
44+
if (token) url.searchParams.set("token", token);
4045

41-
const eventSource = new EventSource(url);
46+
const eventSource = new EventSource(url.toString());
4247

4348
eventSourceRef.current = eventSource;
4449

@@ -304,10 +309,12 @@ export function useSSE(endpoint: string, options: SSEOptions) {
304309
isMountedRef.current = true;
305310

306311
// 构建 SSE URL
307-
const url = buildApiUrl(`/api/sse${endpoint}`);
312+
const token = getAuthToken();
313+
const url = new URL(buildApiUrl(`/api/sse${endpoint}`), window.location.origin);
314+
if (token) url.searchParams.set("token", token);
308315

309316
// 创建 EventSource 实例
310-
const eventSource = new EventSource(url);
317+
const eventSource = new EventSource(url.toString());
311318

312319
// 保存引用
313320
eventSourceRef.current = eventSource;

web/src/lib/hooks/use-system-monitor-ws.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState, useEffect, useRef, useCallback } from "react";
22

3-
import { buildApiUrl } from "@/lib/utils";
3+
import { buildApiUrl, buildWsUrl, getAuthToken } from "@/lib/utils";
44

55
export interface SystemMonitorData {
66
endpointId: number;
@@ -86,10 +86,15 @@ export function useSystemMonitorWS(
8686
setError(null);
8787

8888
try {
89-
// 构建WebSocket URL,直接传递端点ID
90-
const wsUrl = buildApiUrl(
91-
`/api/ws/system-monitor?endpointId=${endpointId}`,
92-
).replace("http", "ws");
89+
const token = getAuthToken();
90+
const url = new URL(
91+
buildApiUrl(`/api/ws/system-monitor?endpointId=${endpointId}`),
92+
window.location.origin,
93+
);
94+
if (token) url.searchParams.set("token", token);
95+
96+
// 构建 WebSocket URL(确保为绝对 ws/wss)
97+
const wsUrl = buildWsUrl(url.toString());
9398

9499
console.log("[System Monitor WS] 连接到:", wsUrl);
95100

web/src/lib/hooks/use-tunnel-monitor-ws.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState, useEffect, useRef, useCallback } from "react";
22

3-
import { buildApiUrl } from "@/lib/utils";
3+
import { buildApiUrl, buildWsUrl, getAuthToken } from "@/lib/utils";
44

55
export interface TunnelMonitorData {
66
instanceId: string;
@@ -74,10 +74,14 @@ export function useTunnelMonitorWS(
7474
setError(null);
7575

7676
try {
77-
// 构建WebSocket URL,直接传递实例ID
78-
const wsUrl = buildApiUrl(
79-
`/api/ws/tunnel-monitor?instanceId=${instanceId}`,
80-
).replace("http", "ws");
77+
const token = getAuthToken();
78+
const url = new URL(
79+
buildApiUrl(`/api/ws/tunnel-monitor?instanceId=${instanceId}`),
80+
window.location.origin,
81+
);
82+
if (token) url.searchParams.set("token", token);
83+
84+
const wsUrl = buildWsUrl(url.toString());
8185

8286
const websocket = new WebSocket(wsUrl);
8387

web/src/lib/utils.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,35 @@ export function buildApiUrl(path: string): string {
2727
return path;
2828
}
2929

30+
/**
31+
* 构建 WebSocket URL(确保为 ws/wss 的绝对 URL)
32+
* @param path WebSocket 路径(通常为 /api/ws/... 或 http(s)://...)
33+
* @returns 完整的 ws/wss URL
34+
*/
35+
export function buildWsUrl(path: string): string {
36+
if (typeof window === "undefined") return path;
37+
38+
// 已经是 ws(s)://
39+
if (path.startsWith("ws://") || path.startsWith("wss://")) return path;
40+
41+
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
42+
43+
// http(s):// -> ws(s)://
44+
if (path.startsWith("http://") || path.startsWith("https://")) {
45+
const url = new URL(path);
46+
url.protocol = wsProtocol;
47+
return url.toString();
48+
}
49+
50+
// /api/ws/... -> ws(s)://host/api/ws/...
51+
return `${wsProtocol}//${window.location.host}${path}`;
52+
}
53+
54+
export function getAuthToken(): string | null {
55+
if (typeof window === "undefined") return null;
56+
return localStorage.getItem("nodepass.token");
57+
}
58+
3059
// 实例缓存
3160
const instanceCache = new Map<string, { data: Instance; timestamp: number }>();
3261
const CACHE_TTL = 60000; // 1分钟缓存时间

web/src/lib/utils/version-check.ts

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/**
2+
* 版本检查工具
3+
* 用于检查是否有新版本可用
4+
*/
5+
6+
/**
7+
* 版本信息接口
8+
*/
9+
export interface VersionInfo {
10+
version: string;
11+
releaseUrl: string;
12+
releaseName: string;
13+
publishedAt: string;
14+
}
15+
16+
/**
17+
* GitHub Release 响应接口
18+
*/
19+
interface GitHubRelease {
20+
tag_name: string;
21+
name: string;
22+
html_url: string;
23+
published_at: string;
24+
prerelease: boolean;
25+
}
26+
27+
/**
28+
* 判断版本是否为测试版
29+
* @param version 版本号字符串
30+
* @returns 是否为测试版
31+
*/
32+
export function isBetaVersion(version: string): boolean {
33+
const betaKeywords = ['beta', 'alpha', 'rc', 'dev', 'preview'];
34+
const versionLower = version.toLowerCase();
35+
36+
return betaKeywords.some(keyword => versionLower.includes(keyword));
37+
}
38+
39+
/**
40+
* 比较两个语义化版本号
41+
* @param version1 版本号1
42+
* @param version2 版本号2
43+
* @returns 如果 version2 > version1 返回 true
44+
*/
45+
export function compareVersions(version1: string, version2: string): boolean {
46+
// 移除 'v' 前缀和 beta 等后缀,只保留数字部分
47+
const cleanVersion = (v: string) => {
48+
return v.replace(/^v/, '').split(/[-+]/)[0];
49+
};
50+
51+
const v1 = cleanVersion(version1);
52+
const v2 = cleanVersion(version2);
53+
54+
const parts1 = v1.split('.').map(Number);
55+
const parts2 = v2.split('.').map(Number);
56+
57+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
58+
const num1 = parts1[i] || 0;
59+
const num2 = parts2[i] || 0;
60+
61+
if (num2 > num1) return true;
62+
if (num2 < num1) return false;
63+
}
64+
65+
return false;
66+
}
67+
68+
/**
69+
* 从 GitHub 获取最新版本信息
70+
* @param currentVersion 当前版本号
71+
* @returns 版本信息,如果没有新版本则返回 null
72+
*/
73+
export async function checkForUpdates(currentVersion: string): Promise<VersionInfo | null> {
74+
try {
75+
const isBeta = isBetaVersion(currentVersion);
76+
let latestVersion: VersionInfo | null = null;
77+
78+
if (isBeta) {
79+
// 测试版:获取所有 releases(包括 pre-release)
80+
const response = await fetch(
81+
'https://api.github.com/repos/NodePassProject/nodepass-core/releases',
82+
{
83+
headers: {
84+
'Accept': 'application/vnd.github.v3+json',
85+
},
86+
}
87+
);
88+
89+
if (!response.ok) {
90+
console.error('Failed to fetch releases:', response.statusText);
91+
return null;
92+
}
93+
94+
const releases: GitHubRelease[] = await response.json();
95+
96+
// 找到最新的版本(可能是正式版或测试版)
97+
if (releases.length > 0) {
98+
const latest = releases[0];
99+
latestVersion = {
100+
version: latest.tag_name,
101+
releaseUrl: latest.html_url,
102+
releaseName: latest.name,
103+
publishedAt: latest.published_at,
104+
};
105+
}
106+
} else {
107+
// 正式版:只获取最新的正式版本
108+
const response = await fetch(
109+
'https://api.github.com/repos/yosebyte/nodepass/releases/latest',
110+
{
111+
headers: {
112+
'Accept': 'application/vnd.github.v3+json',
113+
},
114+
}
115+
);
116+
117+
if (!response.ok) {
118+
console.error('Failed to fetch latest release:', response.statusText);
119+
return null;
120+
}
121+
122+
const release: GitHubRelease = await response.json();
123+
latestVersion = {
124+
version: release.tag_name,
125+
releaseUrl: release.html_url,
126+
releaseName: release.name,
127+
publishedAt: release.published_at,
128+
};
129+
}
130+
131+
// 比较版本号
132+
if (latestVersion && compareVersions(currentVersion, latestVersion.version)) {
133+
return latestVersion;
134+
}
135+
136+
return null;
137+
} catch (error) {
138+
console.error('Error checking for updates:', error);
139+
return null;
140+
}
141+
}
142+
143+
/**
144+
* 格式化发布时间
145+
* @param publishedAt ISO 8601 时间字符串
146+
* @returns 格式化后的时间字符串
147+
*/
148+
export function formatReleaseTime(publishedAt: string): string {
149+
const date = new Date(publishedAt);
150+
const now = new Date();
151+
const diffMs = now.getTime() - date.getTime();
152+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
153+
154+
if (diffDays === 0) {
155+
return '今天';
156+
} else if (diffDays === 1) {
157+
return '昨天';
158+
} else if (diffDays < 7) {
159+
return `${diffDays} 天前`;
160+
} else if (diffDays < 30) {
161+
const weeks = Math.floor(diffDays / 7);
162+
return `${weeks} 周前`;
163+
} else if (diffDays < 365) {
164+
const months = Math.floor(diffDays / 30);
165+
return `${months} 个月前`;
166+
} else {
167+
return date.toLocaleDateString('zh-CN');
168+
}
169+
}

web/src/locales/en-US/dashboard.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,10 @@
9393
},
9494
"select": "Select"
9595
},
96+
"update": {
97+
"newVersionAvailable": "New Version Available",
98+
"defaultMessage": "Click to view update details and download",
99+
"viewRelease": "View"
100+
},
96101
"loading": "Loading..."
97102
}

0 commit comments

Comments
 (0)