feat: 用户个人页面展示当前 VPN 连接信息

- handler/vpn.go: 新增 GetMyVpnConnections,返回当前用户的在线
  VPN 连接列表(IP/连接时间)及 max_conns_per_user 上限
- router.go: 注册 GET /api/me/vpn/connections(普通用户可访问)
- ProfileView.vue: 新增"我的 VPN 连接"区块,含 n/max 徽章、
  IPv4/IPv6/连接时间表格,底部提示异常连接请修改密码
- zh.ts/en.ts: 新增 myVpnConnections/noConnections/
  abnormalConnectionHint 国际化
This commit is contained in:
2026-07-10 13:53:49 +08:00
parent c63440435e
commit aad8fa9848
5 changed files with 93 additions and 0 deletions
+36
View File
@@ -208,6 +208,42 @@ func UpdateVpnSettings(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "设置已更新"})
}
type myVpnConnection struct {
IP string `json:"ip"`
IP6 string `json:"ip6,omitempty"`
ConnectedAt string `json:"connected_at"`
}
func GetMyVpnConnections(c *gin.Context) {
userID, _ := c.Get("user_id")
maxConns := 30
if vpn.VPN != nil {
s := vpn.VPN.Settings()
if s.MaxConnsPerUser > 0 {
maxConns = s.MaxConnsPerUser
}
}
connections := make([]myVpnConnection, 0)
if vpn.VPN != nil && vpn.VPN.Running() {
for _, ci := range vpn.VPN.ClientList() {
if ci.UserID == userID.(uint) {
connections = append(connections, myVpnConnection{
IP: ci.IP,
IP6: ci.IP6,
ConnectedAt: ci.ConnectedAt,
})
}
}
}
c.JSON(http.StatusOK, gin.H{
"max_conns_per_user": maxConns,
"connections": connections,
})
}
type vpnStatusResponse struct {
Enabled bool `json:"enabled"`
Online int `json:"online"`
+1
View File
@@ -26,6 +26,7 @@ func Setup(r *gin.Engine) {
auth.PUT("/me/password", handler.ChangePassword)
auth.GET("/me/sessions", handler.ListMySessions)
auth.DELETE("/me/sessions/:sessionId", handler.RevokeMySession)
auth.GET("/me/vpn/connections", handler.GetMyVpnConnections)
}
admin := r.Group("/api/admin")