521 lines
12 KiB
Go
521 lines
12 KiB
Go
|
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
|
|||
|
}
|