package tui import ( "encoding/json" "fmt" "gitee.ltd/lxh/logger/log" "github.com/charmbracelet/bubbles/table" "github.com/spf13/cast" "os" "strconv" "strings" "time" "wireguard-ui/component" "wireguard-ui/global/constant" "wireguard-ui/http/param" "wireguard-ui/http/vo" "wireguard-ui/service" "wireguard-ui/utils" ) type ClientComponent struct { LoginUser *vo.User Menu []string Clients [][]string } func NewClientComponent(loginUser *vo.User) *ClientComponent { ccp := &ClientComponent{ LoginUser: loginUser, Menu: []string{"[1] 客户端列表", "[2] 查看客户端配置", "[3] 添加客户端", "[4] 编辑客户端", "[5] 删除客户端", "[q] 返回上一级菜单"}, } return ccp } // Menus // @description: 客户端菜单 // @receiver c func (c *ClientComponent) Menus() { fmt.Println("") for _, r := range c.Menu { fmt.Println(" -> " + r) } chooseMenu := readInput("\n请选择: ") switch chooseMenu { case "1": c.List(true) case "2": c.ShowConfig() case "3": c.Add() case "4": c.Edit() case "5": c.Delete() case "q": return } } // ConnectList // @description: 客户端链接列表 // @receiver c // @return string func (c *ClientComponent) ConnectList() { fmt.Println("\n客户端链接列表") connectList, err := component.Wireguard().GetClients() if err != nil { fmt.Println("获取客户端链接列表失败: ", err.Error()) return } var data [][]string for _, peer := range connectList { // 获取客户端链接信息 clientInfo, err := service.Client().GetByPublicKey(peer.PublicKey.String()) if err != nil { continue } var ipAllocation string for _, iaip := range peer.AllowedIPs { ipAllocation += iaip.String() + "," } // 去除一下最右边的逗号 if len(ipAllocation) > 0 { ipAllocation = strings.TrimRight(ipAllocation, ",") } var isOnline = "否" if time.Since(peer.LastHandshakeTime).Minutes() < 3 { isOnline = "是" } data = append(data, []string{clientInfo.Name, clientInfo.Email, ipAllocation, isOnline, utils.FlowCalculation().Parse(peer.TransmitBytes), utils.FlowCalculation().Parse(peer.ReceiveBytes), peer.Endpoint.String(), peer.LastHandshakeTime.Format("2006-01-02 15:04:05")}) } //if len(data) <= 0 { // //data = append(data, []string{"暂无数据"}) // // data = append(data, []string{"名称1", "12345678910@qq.com", "192.168.100.1", "是", "10G", "20G", "1.14.30.133:51280", "2024-12-20 15:07:36"}, []string{"名称2", "12345678910@qq.com", "192.168.100.2", "否", "20G", "40G", "1.14.30.133:51280", "2024-12-22 15:07:36"}) //} title := []table.Column{ { Title: "客户端名称", Width: 20, }, { Title: "联系邮箱", Width: 20, }, { Title: "分配的IP", Width: 30, }, { Title: "是否在线", Width: 10, }, { Title: "接收流量", Width: 10, }, { Title: "传输流量", Width: 10, }, { Title: "链接端点", Width: 30, }, { Title: "最后握手时间", Width: 30, }} Show(GenerateTable(title, data)) return } // List // @description: 客户端列表 // @receiver c func (c *ClientComponent) List(showMenu bool) { title := []table.Column{ { Title: "序号", Width: 5, }, { Title: "ID", Width: 35, }, { Title: "名称", Width: 25, }, { Title: "联系邮箱", Width: 30, }, { Title: "客户端IP", Width: 20, }, { Title: "状态", Width: 10, }, { Title: "离线通知", Width: 10, }, } records, _, err := service.Client().List(param.ClientList{ Page: param.Page{ Current: -1, }, }) if err != nil { fmt.Println("获取客户端列表失败: ", err.Error()) return } fmt.Println("\n客户端列表") var data [][]string for i, client := range records { var status, offlineNotify string if client.Enabled == 1 { status = "启用" } else { status = "禁用" } if client.OfflineMonitoring == 1 { offlineNotify = "开启" } else { offlineNotify = "关闭" } data = append(data, []string{ cast.ToString(i + 1), client.Id, client.Name, client.Email, client.IpAllocationStr, status, offlineNotify, }) } Show(GenerateTable(title, data)) c.Clients = data if showMenu { c.Menus() } return } // ShowConfig // @description: 显示配置 // @receiver c func (c *ClientComponent) ShowConfig() { c.List(false) clientIdx := readInput("请输入客户端序号: ") downloadType := readInput("请输入下载类型(FILE - 文件 | EMAIL - 邮件): ") idx, err := strconv.Atoi(clientIdx) if err != nil { fmt.Println("输入有误: ", err.Error()) return } if idx < 1 || idx > len(c.Clients) { fmt.Println("输入有误") return } client := c.Clients[idx-1] // 取到id clientID := client[1] // 查询客户端信息 clientInfo, err := service.Client().GetByID(clientID) if err != nil { fmt.Println("获取客户端信息失败: ", err.Error()) return } // 渲染配置 var keys vo.Keys _ = json.Unmarshal([]byte(clientInfo.Keys), &keys) globalSet, err := service.Setting().GetWGSetForConfig() if err != nil { fmt.Println("获取全局配置失败: ", err.Error()) return } serverConf, err := service.Setting().GetWGServerForConfig() if err != nil { fmt.Println("获取服务器配置失败: ", err.Error()) return } outPath, err := component.Wireguard().GenerateClientFile(clientInfo, serverConf, globalSet) if err != nil { fmt.Println("生成客户端配置失败: ", err.Error()) return } // 根据不同下载类型执行不同逻辑 switch downloadType { case "FILE": // 二维码 // 读取文件内容 fileContent, err := os.ReadFile(outPath) if err != nil { fmt.Println("读取文件失败: ", err.Error()) return } fmt.Println("\n#请将以下内容复制到客户端配置文件中【不包含本行提示语】") fmt.Println("\n" + string(fileContent)) if err = os.Remove(outPath); err != nil { log.Errorf("删除临时文件失败: %s", err.Error()) } case "EMAIL": // 邮件 if clientInfo.Email == "" { fmt.Println("当前客户端并未配置通知邮箱") return } // 获取邮箱配置 emailConf, err := service.Setting().GetByCode("EMAIL_SMTP") if err != nil { fmt.Println("获取邮箱配置失败,请先到设置页面的【其他】里面添加code为【EMAIL_SMTP】的具体配置") return } err = utils.Mail(emailConf).SendMail(clientInfo.Email, fmt.Sprintf("客户端: %s", clientInfo.Name), "请查收附件", outPath) if err != nil { fmt.Println("发送邮件失败") return } if err = os.Remove(outPath); err != nil { log.Errorf("删除临时文件失败: %s", err.Error()) } fmt.Println("发送邮件成功,请注意查收!") } c.Menus() } // Add // @description: 添加客户端 // @receiver c func (c *ClientComponent) Add() { fmt.Println("\n添加客户端") clientIP, serverIP, err := GenerateIP() if err != nil { fmt.Println("生成客户端IP失败: ", err.Error()) return } keys, err := GenerateKeys() if err != nil { fmt.Println("生成密钥对失败: ", err.Error()) return } var p param.SaveClient p.Name = readInput("请输入客户端名称: ") p.Email = readInput("请输入联系邮箱: ") clientIPIn := readInput("请输入客户端IP(默认自动生成,多个采用 ',' 分割,例如 10.10.0.1/32,10.10.0.2/32): ") if clientIPIn == "" { p.IpAllocation = clientIP } else { p.IpAllocation = strings.Split(clientIPIn, ",") } p.AllowedIps = serverIP p.Keys = ¶m.Keys{ PrivateKey: keys.PrivateKey, PublicKey: keys.PublicKey, PresharedKey: keys.PresharedKey, } var useServerDNS, enabled, offlineNotify constant.Status useServerDNSIn := readInput("是否使用服务器DNS(默认不使用 1 - 是 | 0 - 否 ): ") switch useServerDNSIn { case "1": useServerDNS = constant.Enabled case "0": useServerDNS = constant.Disabled default: useServerDNS = constant.Disabled } enabledIn := readInput("是否启用(默认启用 1 - 是 | 0 - 否 ): ") switch enabledIn { case "1": enabled = constant.Enabled case "0": enabled = constant.Disabled default: enabled = constant.Enabled } offlineNotifyIn := readInput("是否开启离线通知(默认关闭 1 - 是 | 0 - 否 ): ") switch offlineNotifyIn { case "1": offlineNotify = constant.Enabled case "0": offlineNotify = constant.Disabled default: offlineNotify = constant.Disabled } p.UseServerDns = &useServerDNS p.Enabled = &enabled p.OfflineMonitoring = &offlineNotify err = service.Client().SaveClient(p, c.LoginUser) if err != nil { fmt.Println("添加客户端失败: ", err.Error()) return } fmt.Println("添加客户端成功") c.List(true) return } // Edit // @description: 编辑客户端 // @receiver c func (c *ClientComponent) Edit() { fmt.Println("\n编辑客户端") c.List(false) clientIdx := readInput("请输入客户端序号: ") idx, err := strconv.Atoi(clientIdx) if err != nil { fmt.Println("输入有误: ", err.Error()) return } if idx < 1 || idx > len(c.Clients) { fmt.Println("输入有误") return } client := c.Clients[idx-1] // 取到id clientID := client[1] // 查询客户端信息 clientInfo, err := service.Client().GetByID(clientID) if err != nil { fmt.Println("获取客户端信息失败: ", err.Error()) return } var p param.SaveClient p.Id = clientID p.Name = readInput("请输入客户端名称[无需改变请回车跳过,下同]: ") p.Email = readInput("请输入联系邮箱: ") clientIPIn := readInput("请输入客户端IP(默认自动生成,多个采用 ',' 分割,例如 10.10.0.1/32,10.10.0.2/32): ") if clientIPIn == "" { p.IpAllocation = strings.Split(clientInfo.IpAllocation, ",") } else { p.IpAllocation = strings.Split(clientIPIn, ",") } p.AllowedIps = strings.Split(clientInfo.AllowedIps, ",") var keys *param.Keys _ = json.Unmarshal([]byte(clientInfo.Keys), &keys) p.Keys = keys var useServerDNS, enabled, offlineNotify constant.Status useServerDNSIn := readInput("是否使用服务器DNS(默认不使用 1 - 是 | 0 - 否 ): ") switch useServerDNSIn { case "1": useServerDNS = constant.Enabled case "0": useServerDNS = constant.Disabled default: useServerDNS = clientInfo.UseServerDns } enabledIn := readInput("是否启用(默认启用 1 - 是 | 0 - 否 ): ") switch enabledIn { case "1": enabled = constant.Enabled case "0": enabled = constant.Disabled default: enabled = clientInfo.Enabled } offlineNotifyIn := readInput("是否开启离线通知(默认关闭 1 - 是 | 0 - 否 ): ") switch offlineNotifyIn { case "1": offlineNotify = constant.Enabled case "0": offlineNotify = constant.Disabled default: offlineNotify = clientInfo.OfflineMonitoring } p.UseServerDns = &useServerDNS p.Enabled = &enabled p.OfflineMonitoring = &offlineNotify err = service.Client().SaveClient(p, c.LoginUser) if err != nil { fmt.Println("编辑客户端失败: ", err.Error()) return } fmt.Println("编辑客户端成功") c.List(true) return } // Delete // @description: 删除客户端 // @receiver c func (c *ClientComponent) Delete() { fmt.Println("\n删除客户端") c.List(false) clientIdx := readInput("请输入客户端序号: ") idx, err := strconv.Atoi(clientIdx) if err != nil { fmt.Println("输入有误: ", err.Error()) return } if idx < 1 || idx > len(c.Clients) { fmt.Println("输入有误") return } client := c.Clients[idx-1] // 取到id clientID := client[1] if err := service.Client().Delete(clientID); err != nil { fmt.Println("删除客户端失败: ", err.Error()) return } c.List(true) fmt.Println("删除客户端成功") return }