From aaac3fc791c7297aa9ce5ea8cfcdc417ed7bdd26 Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Tue, 28 Jul 2026 19:11:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=95=E5=85=A52.1=E5=BA=94=E5=81=9A?= =?UTF-8?q?=E7=9A=84=E5=85=A8=E6=96=B0UI=E5=8F=98=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.go | 34 +++++++++++++++++- static/app.js | 87 ++++++++++++++++++++++++++++++++++++++++++++++ static/index.html | 84 ++++++++++++++++++++++++-------------------- static/style-3.css | 66 +++++++++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+), 38 deletions(-) diff --git a/api.go b/api.go index 809d438..2f746f4 100644 --- a/api.go +++ b/api.go @@ -6,11 +6,13 @@ import ( "fmt" "io" "io/fs" + "net" "net/http" "os" "strconv" "strings" "text/template" + "time" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" @@ -64,7 +66,7 @@ func SetupRouter() *gin.Engine { auth.POST("/frpc/stop", stopFrpcHandler) auth.GET("/frpc/status", getFrpcStatusHandler) auth.GET("/frpc/log", getFrpcLogHandler) - + auth.GET("/ping", pingHandler) auth.POST("/import/toml", importTomlHandler) auth.GET("/export/toml", ExportTomlHandler) @@ -143,6 +145,36 @@ func registerHandler(c *gin.Context) { }) } +// Ping 延迟检测 +func pingHandler(c *gin.Context) { + target := c.Query("target") + if target == "" { + c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "缺少 target 参数"}) + return + } + + // 获取全局配置(获取服务端端口) + cfg, err := GetGlobalConfig() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "读取配置失败"}) + return + } + + port := cfg.ServerPort + address := net.JoinHostPort(target, strconv.Itoa(port)) + + start := time.Now() + conn, err := net.DialTimeout("tcp", address, 5*time.Second) + if err != nil { + c.JSON(http.StatusOK, gin.H{"code": 1, "msg": "ping 失败", "latency": -1}) + return + } + conn.Close() + + latency := time.Since(start).Milliseconds() + c.JSON(http.StatusOK, gin.H{"code": 0, "latency": latency}) +} + func loginHandler(c *gin.Context) { var req struct { Username string `json:"username"` diff --git a/static/app.js b/static/app.js index 7d3082d..6a04455 100644 --- a/static/app.js +++ b/static/app.js @@ -205,6 +205,93 @@ async function fetchLogsApi() { } } +// ---- 新增:Ping 延迟检测 ---- +const pingLatency = ref(null); // null 表示未接入/失败 +const pingInterval = ref(null); +const PING_INTERVAL_MS = 30000; // 30 秒轮询一次 +const PING_TIMEOUT_MS = 5000; // 5 秒超时 + +// 计算 ping 状态 class +const pingStatusClass = computed(() => { + if (pingLatency.value === null) return 'ping-fail'; + if (pingLatency.value < 1000) return 'ping-good'; + if (pingLatency.value < 5000) return 'ping-slow'; + return 'ping-fail'; +}); + +// 根据状态返回对应 SVG 图标(内联) +const pingIcon = computed(() => { + if (pingLatency.value === null) { + // 未接入/失败 —— 断开图标 + return ` + + + + + + `; + } + if (pingLatency.value < 1000) { + // 延迟好 —— 实心信号图标(青色/绿色) + return ` + + + `; + } + // 延迟一般(1000-5000ms)—— 空心信号图标(黄色/橙色) + return ` + + + `; +}); + +// 执行 Ping 检测 +async function doPing() { + if (!frpcRunning.value) { + pingLatency.value = null; + return; + } + const addr = globalConfig.serverAddr || 'frp.example.com'; + const start = performance.now(); + try { + const res = await fetch(`/api/ping?target=${encodeURIComponent(addr)}`, { + signal: AbortSignal.timeout(PING_TIMEOUT_MS), + }); + if (!res.ok) throw new Error('Ping failed'); + const end = performance.now(); + pingLatency.value = Math.round(end - start); + } catch { + pingLatency.value = null; + } +} + +// 启动 Ping 轮询 +function startPingPolling() { + if (pingInterval.value) return; + doPing(); + pingInterval.value = setInterval(doPing, PING_INTERVAL_MS); +} + +function stopPingPolling() { + if (pingInterval.value) { + clearInterval(pingInterval.value); + pingInterval.value = null; + } +} + +// 在登录后启动,退出时停止 +// 在 loadAllData 成功后调用 startPingPolling() +// 在 doLogout 中调用 stopPingPolling() +// 监听 frpcRunning 变化:如果为 false,清空 pingLatency +watch(frpcRunning, (running) => { + if (!running) { + pingLatency.value = null; + stopPingPolling(); + } else if (loggedIn.value) { + startPingPolling(); + } +}); + // ============================================================ // 7. UI State // ============================================================ diff --git a/static/index.html b/static/index.html index bef17dc..63de43e 100644 --- a/static/index.html +++ b/static/index.html @@ -87,7 +87,6 @@
-
@@ -97,11 +96,17 @@ {{ frpcRunning ? 'frpc 运行中' : 'frpc 已停止' }} + + + + + {{ pingLatency !== null ? pingLatency + 'ms' : '--ms' + }} +
- {{ loginForm.username }}
@@ -114,6 +119,8 @@ @click="activeTab = 'config'">全局配置信息 运行日志 + 用户配置:{{loginForm.username }} @@ -159,7 +166,6 @@ -
@@ -170,34 +176,7 @@
- 首次使用请修改「服务器地址」和「认证令牌」为您的真实 frpc 配置 -
- - -
-
- 账户管理 - {{ loginForm.username }} -
-
-
- - -
-
- - -
-
- - -
- - - -
+ 首次使用请修改「服务器地址」和「认证令牌」为您的真实 frps 配置
@@ -242,31 +221,27 @@ 优化连接性能,减少延迟,frp 官方推荐开启
-
-
-
-
- +
-
+ +
@@ -339,6 +316,39 @@
+ +
+
+

用户配置:{{ loginForm.username }}

+

修改当前账户的登录密码

+
+ +
+
+ 账户管理 + {{ loginForm.username }} +
+
+
+ + +
+
+ + +
+
+ + +
+ + + +
+
+
diff --git a/static/style-3.css b/static/style-3.css index 73a3c6b..c0b3a59 100644 --- a/static/style-3.css +++ b/static/style-3.css @@ -734,4 +734,70 @@ select:disabled { .dialog-card { padding: 20px; } +} + +/* ---------- Ping 延迟显示 ---------- */ +.status-wrapper { + display: flex; + align-items: center; + gap: 12px; +} + +.ping-display { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + font-weight: 500; + padding: 2px 8px 2px 4px; + border-radius: 12px; + background: rgba(255, 255, 255, 0.04); + transition: background 0.3s; +} + +.ping-display .ping-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; +} + +.ping-display .ping-icon svg { + width: 100%; + height: 100%; +} + +/* 状态颜色 */ +.ping-good .ping-icon { + color: #63e2b7; +} +.ping-good .ping-value { + color: #63e2b7; +} + +.ping-slow .ping-icon { + color: #f0c040; +} +.ping-slow .ping-value { + color: #f0c040; +} + +.ping-fail .ping-icon { + color: #f87171; +} +.ping-fail .ping-value { + color: #f87171; +} + +/* 响应式调整 */ +@media (max-width: 768px) { + .ping-display { + font-size: 11px; + padding: 1px 6px 1px 2px; + } + .ping-display .ping-icon { + width: 14px; + height: 14px; + } } \ No newline at end of file