Files
navi/internal/config/profiles.go
T
M4andCursor 6b6c13c933 Release 3.8.1.1: lighter idle UI poll, core cache, multicore ping.
Reduce idle CPU (cheap PollUI, binary cache, logbuf cap, DOM fingerprint),
speed ping/AWG HostPort, CopyBuffer pool; ship arm64 build and update notes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 02:27:17 +03:00

235 lines
5.8 KiB
Go

package config
import (
"fmt"
"strings"
)
// ProfileInfo is a safe summary for UI lists (no secrets beyond proxy URI the user already owns).
type ProfileInfo struct {
Name string `json:"name"`
Protocol string `json:"protocol"`
Proxy string `json:"proxy"`
Host string `json:"host"`
Active bool `json:"active"`
}
// ListProfiles returns UI-friendly profile summaries.
func (c *Config) ListProfiles() []ProfileInfo {
out := make([]ProfileInfo, 0, len(c.Profiles))
for _, p := range c.Profiles {
out = append(out, ProfileInfo{
Name: p.Name,
Protocol: string(p.Protocol),
Proxy: p.Proxy,
Host: proxyHost(p.Proxy),
Active: p.Name == c.Active,
})
}
return out
}
// ListProfilesForPoll is like ListProfiles but omits Proxy URIs (cheaper idle polls).
func (c *Config) ListProfilesForPoll() []ProfileInfo {
out := make([]ProfileInfo, 0, len(c.Profiles))
for _, p := range c.Profiles {
out = append(out, ProfileInfo{
Name: p.Name,
Protocol: string(p.Protocol),
Host: proxyHost(p.Proxy),
Active: p.Name == c.Active,
})
}
return out
}
// RedactProfileList clears Proxy URIs from a profile list (host/protocol remain).
// Use for periodic UI polls so credentials are not echoed for every profile.
func RedactProfileList(in []ProfileInfo) []ProfileInfo {
out := make([]ProfileInfo, len(in))
for i, p := range in {
out[i] = p
out[i].Proxy = ""
}
return out
}
func RedactProxyURI(proxy string) string {
proxy = strings.TrimSpace(proxy)
if proxy == "" {
return ""
}
lower := strings.ToLower(proxy)
if strings.Contains(lower, "[interface]") || strings.Contains(lower, "privatekey") {
return "[конфиг скрыт — откройте редактор профиля]"
}
if i := strings.Index(proxy, "://"); i >= 0 {
scheme := proxy[:i+3]
rest := proxy[i+3:]
if at := strings.LastIndex(rest, "@"); at >= 0 {
return scheme + "***@" + rest[at+1:]
}
return scheme + rest
}
if at := strings.LastIndex(proxy, "@"); at >= 0 {
return "***@" + proxy[at+1:]
}
return proxyHost(proxy)
}
func proxyHost(proxy string) string {
proxy = strings.TrimSpace(proxy)
if proxy == "" {
return ""
}
lower := strings.ToLower(proxy)
if strings.Contains(lower, "[interface]") {
for _, line := range strings.Split(proxy, "\n") {
line = strings.TrimSpace(line)
if len(line) >= 9 && strings.EqualFold(line[:8], "endpoint") {
_, val, ok := strings.Cut(line, "=")
if !ok {
_, val, ok = strings.Cut(line, ":")
}
if ok {
return strings.TrimSpace(val)
}
}
}
}
if i := strings.Index(proxy, "://"); i >= 0 {
proxy = proxy[i+3:]
}
if at := strings.LastIndex(proxy, "@"); at >= 0 {
proxy = proxy[at+1:]
}
if i := strings.IndexAny(proxy, "/?#"); i >= 0 {
proxy = proxy[:i]
}
return proxy
}
// SetActive switches the active profile name.
func (c *Config) SetActive(name string) error {
name = strings.TrimSpace(name)
for _, p := range c.Profiles {
if p.Name == name {
c.Active = name
return nil
}
}
return fmt.Errorf("profile %q not found", name)
}
// UpsertProfile creates or updates a profile by name.
func (c *Config) UpsertProfile(name, proxy string) error {
return c.UpsertProfileWithProtocol(name, proxy, "")
}
// UpsertProfileWithProtocol creates/updates a profile and optionally sets protocol.
// The upserted profile becomes Active.
func (c *Config) UpsertProfileWithProtocol(name, proxy string, proto Protocol) error {
return c.upsertProfile(name, proxy, proto, true)
}
// UpsertProfileKeepActive creates/updates a profile without changing Active.
func (c *Config) UpsertProfileKeepActive(name, proxy string, proto Protocol) error {
return c.upsertProfile(name, proxy, proto, false)
}
func (c *Config) upsertProfile(name, proxy string, proto Protocol, activate bool) error {
name = strings.TrimSpace(name)
if name == "" {
return fmt.Errorf("имя профиля пустое")
}
proxy = strings.TrimSpace(proxy)
if proto == "" {
proto = DetectProtocol(proxy)
}
if proto == "" {
proto = ProtocolNaive
}
for i := range c.Profiles {
if c.Profiles[i].Name == name {
c.Profiles[i].Proxy = proxy
c.Profiles[i].Protocol = proto
if len(c.Profiles[i].Listen) == 0 {
c.Profiles[i].Listen = defaultListen()
}
if activate {
c.Active = name
}
return nil
}
}
c.Profiles = append(c.Profiles, Profile{
Name: name,
Protocol: proto,
Proxy: proxy,
Listen: defaultListen(),
})
if activate {
c.Active = name
}
return nil
}
// RenameProfile renames a profile and keeps active pointer in sync.
func (c *Config) RenameProfile(oldName, newName string) error {
oldName = strings.TrimSpace(oldName)
newName = strings.TrimSpace(newName)
if oldName == "" || newName == "" {
return fmt.Errorf("имя профиля пустое")
}
if oldName == newName {
return nil
}
for _, p := range c.Profiles {
if p.Name == newName {
return fmt.Errorf("профиль %q уже существует", newName)
}
}
for i := range c.Profiles {
if c.Profiles[i].Name == oldName {
c.Profiles[i].Name = newName
if c.Active == oldName {
c.Active = newName
}
return nil
}
}
return fmt.Errorf("profile %q not found", oldName)
}
// DeleteProfile removes a profile. Keeps at least one profile.
func (c *Config) DeleteProfile(name string) error {
name = strings.TrimSpace(name)
if len(c.Profiles) <= 1 {
return fmt.Errorf("нельзя удалить последний профиль")
}
idx := -1
for i, p := range c.Profiles {
if p.Name == name {
idx = i
break
}
}
if idx < 0 {
return fmt.Errorf("profile %q not found", name)
}
c.Profiles = append(c.Profiles[:idx], c.Profiles[idx+1:]...)
if c.Active == name {
c.Active = c.Profiles[0].Name
}
return nil
}
func defaultListen() []string {
return []string{
"socks://127.0.0.1:1080",
"http://127.0.0.1:1081",
}
}