Release 4.2.0+1: store-compliance формулировки и UX — убраны все цены/продажи из приложения (оффер «3 дня бесплатно» без ₽), «Активировать ключ» → «Добавить конфигурацию» (URL-ссылка, placeholder https://), блок «Ключ доступа» заменён статусом «Конфигурация добавлена/не добавлена» (URL не отображается), глобально Подписка→Конфигурация/Ваучер→URL-ссылка/Тариф→Профиль в UI и Go-строках; большая круглая кнопка ПОДКЛЮЧИТЬСЯ/ОТКЛЮЧИТЬСЯ со статусом, убран футер «Готово к подключению», протокол в списке — буквенный бейдж только при отличии от активного, трафик «ДОСТУПНО X ГБ» + «Активен до DD.MM.YYYY» + процент; пинг отдельного сервера кнопкой в строке, экран Статистика (за сегодня: трафик/время в сети/сессии + график 7 дней из localStorage), Kill Switch для VPN-режима (blackhole-маршруты сохраняются при обрыве туннеля, для прокси — системный прокси остаётся на мёртвый порт), тумблер «Авто-лучший сервер» в Настройках. Windows 4.2.0.1.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+58
-3
@@ -62,6 +62,10 @@ type UIState struct {
|
||||
Mode string `json:"mode"`
|
||||
VPNSupported bool `json:"vpn_mode_supported"`
|
||||
VPNActive bool `json:"vpn_active"`
|
||||
// KillSwitch is the persisted setting; KillSwitchActive means the tunnel
|
||||
// died unexpectedly and traffic is blocked until disconnect/reconnect.
|
||||
KillSwitch bool `json:"kill_switch"`
|
||||
KillSwitchActive bool `json:"kill_switch_active,omitempty"`
|
||||
}
|
||||
|
||||
type PingBestResult struct {
|
||||
@@ -141,9 +145,11 @@ func (a *App) GetState() (UIState, error) {
|
||||
Remnawave: a.Mgr.RemnawaveSettings(),
|
||||
Hy2: a.Mgr.ActiveHy2Options(),
|
||||
StorePackaged: update.IsStorePackaged(),
|
||||
Mode: a.Mgr.Mode(),
|
||||
VPNSupported: a.Mgr.VPNModeSupported(),
|
||||
VPNActive: st.VPNActive,
|
||||
Mode: a.Mgr.Mode(),
|
||||
VPNSupported: a.Mgr.VPNModeSupported(),
|
||||
VPNActive: st.VPNActive,
|
||||
KillSwitch: cfg.KillSwitch,
|
||||
KillSwitchActive: st.KillSwitchActive,
|
||||
}
|
||||
if out.Protocol == "" {
|
||||
if p, err := cfg.ActiveProfile(); err == nil {
|
||||
@@ -281,6 +287,51 @@ func (a *App) PingServers() ([]netcheck.Result, error) {
|
||||
return a.runPings()
|
||||
}
|
||||
|
||||
// PingServer measures latency of a single server (📊 button on a server row)
|
||||
// and merges the result into the cached ping list.
|
||||
func (a *App) PingServer(name string) (netcheck.Result, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
a.mu.Lock()
|
||||
list := a.Mgr.Profiles()
|
||||
a.mu.Unlock()
|
||||
var target *netcheck.Target
|
||||
for _, p := range list {
|
||||
if p.Name == name {
|
||||
target = &netcheck.Target{Name: p.Name, Protocol: config.Protocol(p.Protocol), Proxy: p.Proxy}
|
||||
break
|
||||
}
|
||||
}
|
||||
if target == nil {
|
||||
return netcheck.Result{}, fmt.Errorf("сервер %q не найден", name)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
||||
defer cancel()
|
||||
out := netcheck.PingAll(ctx, []netcheck.Target{*target})
|
||||
if len(out) == 0 {
|
||||
return netcheck.Result{}, fmt.Errorf("не удалось измерить пинг")
|
||||
}
|
||||
res := out[0]
|
||||
a.mu.Lock()
|
||||
replaced := false
|
||||
for i := range a.Pings {
|
||||
if a.Pings[i].Name == res.Name {
|
||||
a.Pings[i] = res
|
||||
replaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !replaced {
|
||||
a.Pings = append(a.Pings, res)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SetKillSwitch persists the leak-protection setting.
|
||||
func (a *App) SetKillSwitch(enabled bool) error {
|
||||
return a.Mgr.SetKillSwitch(enabled)
|
||||
}
|
||||
|
||||
func (a *App) PingBest(autoConnect bool) (PingBestResult, error) {
|
||||
pings, err := a.runPings()
|
||||
out := PingBestResult{Pings: pings}
|
||||
@@ -533,6 +584,10 @@ func (a *App) dispatch(name string, args []json.RawMessage) (any, error) {
|
||||
return nil, a.OpenShopURL(arg(args, 0, ""))
|
||||
case "pingServers":
|
||||
return a.PingServers()
|
||||
case "pingServer":
|
||||
return a.PingServer(arg(args, 0, ""))
|
||||
case "setKillSwitch":
|
||||
return nil, a.SetKillSwitch(arg(args, 0, false))
|
||||
case "pingBest":
|
||||
return a.PingBest(arg(args, 0, false))
|
||||
case "checkUpdate":
|
||||
|
||||
Reference in New Issue
Block a user