Files
Go-VPN-Client/internal/gui/advanced.go

236 lines
7.7 KiB
Go

package gui
import (
"fmt"
"sort"
"strings"
"time"
"gioui.org/layout"
"gioui.org/unit"
"gioui.org/widget/material"
"vpn-client/internal/config"
"vpn-client/internal/vless"
"vpn-client/internal/wireguard"
)
func (u *ui) addWireGuardManual() {
name := strings.TrimSpace(u.wgNameEditor.Text())
configText := strings.TrimSpace(u.wgConfigEditor.Text())
if name == "" || configText == "" {
u.message = "WireGuard name and config text are required."
u.messageColor = dangerColor
return
}
if err := wireguard.AddConfig(name, configText); err != nil {
u.message = err.Error()
u.messageColor = dangerColor
return
}
u.wgNameEditor.SetText("")
u.wgConfigEditor.SetText("")
u.message = "WireGuard config added."
u.messageColor = successColor
u.refresh()
}
func (u *ui) addWireGuardFromFile() {
name := strings.TrimSpace(u.wgFileNameEditor.Text())
filePath := strings.TrimSpace(u.wgFilePathEditor.Text())
if name == "" || filePath == "" {
u.message = "WireGuard name and file path are required."
u.messageColor = dangerColor
return
}
if err := wireguard.AddConfigFromFile(name, filePath); err != nil {
u.message = err.Error()
u.messageColor = dangerColor
return
}
u.wgFileNameEditor.SetText("")
u.wgFilePathEditor.SetText("")
u.message = "WireGuard config imported from file."
u.messageColor = successColor
u.refresh()
}
func (u *ui) deleteWireGuardConfig(name string) {
if err := wireguard.DeleteConfig(name); err != nil {
u.message = err.Error()
u.messageColor = dangerColor
return
}
u.message = "WireGuard config removed."
u.messageColor = successColor
u.refresh()
}
func (u *ui) subscriptionConfigs(name string) []config.VLESSConfig {
items := make([]config.VLESSConfig, 0)
for _, cfg := range u.configs.VLESS {
if cfg.Subscription == name {
items = append(items, cfg)
}
}
return items
}
func (u *ui) subscriptionSummary(name string) string {
items := u.subscriptionConfigs(name)
if len(items) == 0 {
return "No imported configs yet."
}
preview := strings.TrimPrefix(items[0].Name, fmt.Sprintf("[%s] ", name))
if len(items) == 1 {
return fmt.Sprintf("1 imported config: %s", trimForPreview(preview, 42))
}
return fmt.Sprintf("%d imported configs. First: %s", len(items), trimForPreview(preview, 36))
}
func (u *ui) testSubscriptionConfigs(name string) error {
items := u.subscriptionConfigs(name)
if len(items) == 0 {
return fmt.Errorf("no imported configs found for %s", name)
}
type result struct {
name string
ping float64
ok bool
}
results := make([]result, 0, len(items))
for _, cfg := range items {
ok, ping, _ := vless.PingServer(cfg.URL, 3*time.Second)
results = append(results, result{
name: strings.TrimPrefix(cfg.Name, fmt.Sprintf("[%s] ", name)),
ping: ping,
ok: ok,
})
}
successful := results[:0]
for _, r := range results {
if r.ok {
successful = append(successful, r)
}
}
if len(successful) == 0 {
return fmt.Errorf("all %d configs from %s are unreachable", len(results), name)
}
sort.Slice(successful, func(i, j int) bool { return successful[i].ping < successful[j].ping })
best := successful[0]
u.message = fmt.Sprintf("%s: %d/%d reachable, best %s at %.0f ms.", name, len(successful), len(results), trimForPreview(best.name, 34), best.ping)
u.messageColor = successColor
return nil
}
func (u *ui) layoutWireGuard(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Spacing: layout.SpaceBetween}.Layout(
gtx,
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return u.card(gtx, panelColor, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(18)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.H5(u.theme, "Add manually")
lbl.Color = textColor
return lbl.Layout(gtx)
}),
layout.Rigid(spacerH(10)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions { return u.layoutEditor(gtx, &u.wgNameEditor, "Config name") }),
layout.Rigid(spacerH(10)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutEditor(gtx, &u.wgConfigEditor, "WireGuard config text")
}),
layout.Rigid(spacerH(12)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutButton(gtx, &u.addWGManualBtn, "Save manual config", accentColor)
}),
)
})
})
}),
layout.Rigid(spacerW(12)),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return u.card(gtx, panelColor, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(18)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.H5(u.theme, "Import from file")
lbl.Color = textColor
return lbl.Layout(gtx)
}),
layout.Rigid(spacerH(10)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutEditor(gtx, &u.wgFileNameEditor, "Config name")
}),
layout.Rigid(spacerH(10)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutEditor(gtx, &u.wgFilePathEditor, "Path to .conf file")
}),
layout.Rigid(spacerH(12)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutButton(gtx, &u.addWGFileBtn, "Import file", accentSoftColor)
}),
)
})
})
}),
)
}),
layout.Rigid(spacerH(16)),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return u.card(gtx, panelColor, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(18)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
if len(u.configs.WireGuard) == 0 {
lbl := material.Body1(u.theme, "No WireGuard configs yet.")
lbl.Color = mutedColor
return lbl.Layout(gtx)
}
return u.wgList.Layout(gtx, len(u.configs.WireGuard), func(gtx layout.Context, i int) layout.Dimensions {
cfg := u.configs.WireGuard[i]
return layout.Inset{Bottom: unit.Dp(12)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return u.card(gtx, panelAltColor, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(16)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.H6(u.theme, cfg.Name)
lbl.Color = textColor
return lbl.Layout(gtx)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
meta := material.Body2(u.theme, trimForPreview(cfg.Config, 110))
meta.Color = mutedColor
return meta.Layout(gtx)
}),
layout.Rigid(spacerH(12)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutButton(gtx, &u.wgConnectBtns[i], "Connect", accentColor)
}),
layout.Rigid(spacerW(8)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutButton(gtx, &u.wgDeleteBtns[i], "Delete", dangerColor)
}),
)
}),
)
})
})
})
})
})
})
}),
)
}