UI/UX 优化: - 登录页分屏布局 + 可访问性修复 + 内联错误提示 - 侧栏菜单分组(专线运维/资源管理/系统) + 图标修正 + 子路由高亮 - Dashboard 统计卡渐变图标 + 空状态引导 + 统计卡可点击 - 创建向导: 依赖检测门控 + 高级折叠 + 必填校验 + WireGuard推荐标 - 专线列表: 空状态CTA + 操作列「更多」下拉 + 状态筛选 + SSE日志流 - 协议品牌SVG图标(ProtocolIcon组件)替换emoji - 设计令牌系统(tokens.css)统一颜色/间距/圆角/阴影 功能增强: - 在线会话管理(sessionmgr): 跨协议会话同步 + 踢下线 + 连接审计日志 - 流量配额引擎(trafficmgr): 配额检查 + 超额动作(block/warn/throttle) + 周期重置 - 异常流量告警: 突发流量检测(>90MB/15s) + 审计记录 - 节点SSH凭证管理: 密码/私钥认证 + 即时测试连接 + AES-GCM加密存储 - 协议二进制管理(binmgr): FRP/OpenVPN GitHub下载 + 系统包安装引导(apt/yum/dnf) - 右上角修改密码弹窗 + MFA独立验证页 + 密码强度提示 - 用户管理: 批量导入 + LDAP浏览导入 + 平台用户导入VPN拨号用户 - 404路由 + IPSec对接页可达性修复 - MFA备用码持久化弹窗(替代Toast) - 轮询可见性守卫 + ECharts resize修复 后端: - 3张新表: VPNSession/ConnectionLog/TrafficPolicy - monitor增强: 会话同步+配额检查+异常告警+到期审计 - changePassword统一8位+新旧不同校验 - openvpn-otp文案专业化(移除抄袭感) - SSE日志流(token降级query参数)
111 lines
2.8 KiB
Go
111 lines
2.8 KiB
Go
// VPN 管理平台 —— 单二进制交付(前端静态资源内嵌) // Logan.Li
|
||
package main
|
||
|
||
import (
|
||
"embed"
|
||
"io/fs"
|
||
"log"
|
||
"net/http"
|
||
"os"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"vpn-platform/backend/internal/api"
|
||
"vpn-platform/backend/internal/core"
|
||
"vpn-platform/backend/internal/monitor"
|
||
"vpn-platform/backend/internal/service"
|
||
"vpn-platform/backend/internal/service/custom"
|
||
"vpn-platform/backend/internal/service/frp"
|
||
"vpn-platform/backend/internal/service/ikev2"
|
||
"vpn-platform/backend/internal/service/ipsec"
|
||
"vpn-platform/backend/internal/service/l2tp"
|
||
"vpn-platform/backend/internal/service/openvpn"
|
||
"vpn-platform/backend/internal/service/wireguard"
|
||
"vpn-platform/backend/internal/store"
|
||
)
|
||
|
||
//go:embed all:dist
|
||
var webFS embed.FS
|
||
|
||
func main() {
|
||
gin.SetMode(gin.ReleaseMode)
|
||
|
||
// 数据目录:默认 ./data,可用 VPN_DATA_DIR 覆盖
|
||
dataDir := service.BaseDir()
|
||
if err := store.Init(dataDir); err != nil {
|
||
log.Fatalf("初始化存储失败: %v", err)
|
||
}
|
||
// OpenVPN invokes this same, already-installed binary from its
|
||
// auth-user-pass-verify hook. Keep it before HTTP startup so the hook has a
|
||
// short-lived process and a meaningful exit status.
|
||
if len(os.Args) > 1 && os.Args[1] == "openvpn-auth" {
|
||
if err := openvpn.RunAuthHelper(os.Args[2:]); err != nil {
|
||
log.Printf("OpenVPN 用户认证失败: %v", err)
|
||
os.Exit(1)
|
||
}
|
||
return
|
||
}
|
||
|
||
// 注册内置协议
|
||
mgr := core.NewManager()
|
||
mgr.Register(wireguard.New())
|
||
mgr.Register(ipsec.New())
|
||
mgr.Register(ikev2.New())
|
||
mgr.Register(l2tp.New())
|
||
mgr.Register(frp.New())
|
||
mgr.Register(openvpn.New())
|
||
// 加载数据库中的自定义协议插件
|
||
custom.LoadAll(mgr)
|
||
|
||
r := gin.New()
|
||
r.Use(gin.Recovery())
|
||
|
||
srv := api.NewServer(mgr)
|
||
srv.RegisterRoutes(r)
|
||
|
||
// 内嵌前端(SPA 回退到 index.html)
|
||
dist, err := fs.Sub(webFS, "dist")
|
||
if err != nil {
|
||
log.Fatalf("内嵌资源损坏: %v", err)
|
||
}
|
||
fileServer := http.FileServer(http.FS(dist))
|
||
r.NoRoute(func(c *gin.Context) {
|
||
path := strings.TrimPrefix(c.Request.URL.Path, "/")
|
||
if path == "" {
|
||
path = "index.html"
|
||
}
|
||
if f, err := dist.Open(path); err == nil {
|
||
f.Close()
|
||
fileServer.ServeHTTP(c.Writer, c.Request)
|
||
return
|
||
}
|
||
// SPA fallback
|
||
c.Request.URL.Path = "/"
|
||
fileServer.ServeHTTP(c.Writer, c.Request)
|
||
})
|
||
|
||
// 状态采集 + 自启恢复
|
||
monitor.Start(mgr, 15*time.Second)
|
||
monitor.AutoStartAll(mgr)
|
||
|
||
addr := os.Getenv("VPN_LISTEN_ADDR")
|
||
if addr == "" {
|
||
addr = ":8080"
|
||
}
|
||
log.Printf("VPN 管理平台已启动: http://localhost%s (默认账号 admin / admin123)", addr)
|
||
log.Printf("协议依赖检测:")
|
||
for _, p := range mgr.List() {
|
||
ok, dep := p.Available()
|
||
mark := "✓"
|
||
if !ok {
|
||
mark = "✗"
|
||
}
|
||
log.Printf(" [%s] %-10s %s", mark, p.Name(), dep)
|
||
}
|
||
if err := r.Run(addr); err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
}
|