feat: 全面优化 — UI/UX重构 + 流量审计与会话管理 + 协议部署
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参数)
This commit is contained in:
1 parent
58c036c34c
commit
1e2f8ef24f
43 files changed
+6783
-460
No files matched your search
@@ -59,6 +59,27 @@ func (s *Service) Validate(cfg map[string]interface{}) (map[string]interface{},
|
||||
for k, v := range cfg {
|
||||
out[k] = v
|
||||
}
|
||||
// 厂商模板优先填空字段,再回落到通用默认值。
|
||||
if key := service.Str(out, "vendor_template"); key != "" {
|
||||
for _, tmpl := range Templates() {
|
||||
if tmpl.Key != key {
|
||||
continue
|
||||
}
|
||||
if service.Str(out, "ike_version") == "" {
|
||||
out["ike_version"] = tmpl.IKEVersion
|
||||
}
|
||||
if service.Str(out, "ike_proposal") == "" {
|
||||
out["ike_proposal"] = tmpl.IKEProposal
|
||||
}
|
||||
if service.Str(out, "esp_proposal") == "" {
|
||||
out["esp_proposal"] = tmpl.ESPProposal
|
||||
}
|
||||
if service.Str(out, "dpd_action") == "" {
|
||||
out["dpd_action"] = tmpl.DPDAction
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if service.Str(out, "ike_version") == "" {
|
||||
out["ike_version"] = "ikev2"
|
||||
}
|
||||
@@ -77,6 +98,12 @@ func (s *Service) Validate(cfg map[string]interface{}) (map[string]interface{},
|
||||
if service.Str(out, "remote_id") == "" {
|
||||
out["remote_id"] = "@vpn-remote"
|
||||
}
|
||||
if service.Num(out, "ike_lifetime", 0) == 0 {
|
||||
out["ike_lifetime"] = 86400
|
||||
}
|
||||
if service.Str(out, "pfs_group") == "" {
|
||||
out["pfs_group"] = "modp2048"
|
||||
}
|
||||
remoteGateway := service.Str(out, "remote_gateway")
|
||||
// StrongSwan accepts both literal addresses and DNS names. Keep DNS support
|
||||
// for peers with dynamic addresses, while rejecting syntax that can alter the
|
||||
@@ -347,18 +374,73 @@ func (s *Service) Status(t *model.Tunnel) (core.Status, error) {
|
||||
if bin, ok := service.LookBin("swanctl"); ok {
|
||||
out, err := service.Run(bin, "--list-sas")
|
||||
child := fmt.Sprintf("site-%d", t.ID)
|
||||
if err == nil && strings.Contains(out, child) && strings.Contains(out, "INSTALLED") {
|
||||
st := service.NowStatus(true, "running", "IKE/Child SA 已建立")
|
||||
return st, nil
|
||||
}
|
||||
ike := fmt.Sprintf("tunnel-%d", t.ID)
|
||||
if err != nil {
|
||||
return service.NowStatus(false, "error", "无法读取 strongSwan SA 状态: "+err.Error()), nil
|
||||
}
|
||||
if strings.Contains(out, child) && strings.Contains(out, "INSTALLED") {
|
||||
detail := "IKE/Child SA 已建立"
|
||||
// 从 swanctl 输出中尽量提取对端地址与字节计数(格式因版本略有差异)。
|
||||
if peer := extractSwanPeer(out, ike); peer != "" {
|
||||
detail += ",对端 " + peer
|
||||
}
|
||||
if rx, tx, ok := extractSwanBytes(out, child); ok {
|
||||
st := service.NowStatus(true, "running", detail)
|
||||
st.RxBytes = rx
|
||||
st.TxBytes = tx
|
||||
return st, nil
|
||||
}
|
||||
return service.NowStatus(true, "running", detail), nil
|
||||
}
|
||||
if strings.Contains(out, ike) {
|
||||
return service.NowStatus(false, "stopped", "IKE SA 存在但 Child SA 未 INSTALLED"), nil
|
||||
}
|
||||
return service.NowStatus(false, "stopped", "IPSec Child SA 未建立"), nil
|
||||
}
|
||||
return service.NowStatus(false, "undeployed", "配置已生成,但未检测到 strongSwan(swanctl),尚未部署"), nil
|
||||
}
|
||||
|
||||
func extractSwanPeer(out, ikeName string) string {
|
||||
// 典型行: tunnel-1: #1, ESTABLISHED, ... 1.2.3.4[... ]...5.6.7.8[...]
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
if !strings.Contains(line, ikeName) {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
for _, f := range fields {
|
||||
if strings.Contains(f, "[") && strings.Count(f, ".") >= 3 {
|
||||
return strings.Split(f, "[")[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractSwanBytes(out, childName string) (rx, tx int64, ok bool) {
|
||||
// 寻找含 child 名与 bytes_i/bytes_o 的行
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
if !strings.Contains(line, childName) {
|
||||
continue
|
||||
}
|
||||
// 兼容 "123 bytes_i, 456 bytes_o" 与 "bytes_i/bytes_o"
|
||||
parts := strings.Fields(line)
|
||||
for i, p := range parts {
|
||||
if strings.Contains(p, "bytes_i") && i > 0 {
|
||||
fmt.Sscanf(parts[i-1], "%d", &rx)
|
||||
ok = true
|
||||
}
|
||||
if strings.Contains(p, "bytes_o") && i > 0 {
|
||||
fmt.Sscanf(parts[i-1], "%d", &tx)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
func (s *Service) Delete(t *model.Tunnel) error { return s.Stop(t) }
|
||||
|
||||
func (s *Service) unload(bin, confPath string) error {
|
||||
|
||||
Reference in new issue
Block a user