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:
@@ -64,6 +64,10 @@ type uiState struct {
|
||||
Mode string `json:"mode"`
|
||||
VPNSupported bool `json:"vpn_mode_supported"`
|
||||
VPNActive bool `json:"vpn_active"`
|
||||
// KillSwitch — persisted setting; KillSwitchActive — tunnel died and
|
||||
// traffic is intentionally blocked until disconnect / reconnect.
|
||||
KillSwitch bool `json:"kill_switch"`
|
||||
KillSwitchActive bool `json:"kill_switch_active,omitempty"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -146,7 +150,9 @@ func main() {
|
||||
mustBind(w, "installCore", a.installCore)
|
||||
mustBind(w, "openURL", openURL)
|
||||
mustBind(w, "pingServers", a.pingServers)
|
||||
mustBind(w, "pingServer", a.pingServer)
|
||||
mustBind(w, "pingBest", a.pingBest)
|
||||
mustBind(w, "setKillSwitch", a.setKillSwitch)
|
||||
mustBind(w, "checkUpdate", a.checkUpdate)
|
||||
mustBind(w, "applyUpdate", a.applyUpdate)
|
||||
mustBind(w, "saveHy2", a.saveHy2)
|
||||
@@ -225,9 +231,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 {
|
||||
@@ -380,6 +388,50 @@ func (a *app) pingServers() ([]netcheck.Result, error) {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// pingServer measures latency of one 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
|
||||
}
|
||||
|
||||
func (a *app) setKillSwitch(enabled bool) error {
|
||||
return a.mgr.SetKillSwitch(enabled)
|
||||
}
|
||||
|
||||
// pingBest measures all nodes, activates the fastest OK one, and optionally connects.
|
||||
func (a *app) pingBest(autoConnect bool) (pingBestResult, error) {
|
||||
pings, err := a.runPings()
|
||||
|
||||
Reference in New Issue
Block a user