diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 1ac7087..89dbd85 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -13,8 +13,8 @@ android { minSdk = 26 targetSdk = 35 // versionCode = major*1_000_000 + minor*10_000 + patch*100 + build - versionCode = 4_020_001 - versionName = "4.2.0+1" + versionCode = 4_021_001 + versionName = "4.2.1+1" vectorDrawables.useSupportLibrary = true ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64") diff --git a/dist/navis-release/update.json b/dist/navis-release/update.json index 0f07365..2569d01 100644 --- a/dist/navis-release/update.json +++ b/dist/navis-release/update.json @@ -1,48 +1,48 @@ { - "version": "4.2.0", - "notes": "4.2.0: обновлённые формулировки, Kill Switch, статистика, кнопка подключения", - "platform": "windows-amd64", - "os": "windows", - "arch": "amd64", - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe", - "sha256": "64da79ddf7cd5694bf93c4c110bf0e1281a20651e1e868a960562d5c1a15bd0d", - "mandatory": false, - "platforms": { - "windows-amd64": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe", - "sha256": "64da79ddf7cd5694bf93c4c110bf0e1281a20651e1e868a960562d5c1a15bd0d", - "os": "windows", - "arch": "amd64" - }, - "darwin-arm64": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis", - "sha256": "01a40b1cce4e2fb3a38eb80f0815c295a00af11e4288f3b9e5c0b58ae336f28d", - "os": "darwin", - "arch": "arm64", - "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.dmg", - "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.app.zip" - }, - "darwin-amd64": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis", - "sha256": "19a066cfc7f7c3a291a220482b28e66a459d091c19c5351818c401b982bc65b3", - "os": "darwin", - "arch": "amd64", - "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.dmg", - "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.app.zip" - }, - "darwin-universal": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis", - "sha256": "d53dba8256fd6f7b62d1ce9bbad741af087ae44fec942bf2782b9c8f4e36cef2", - "os": "darwin", - "arch": "universal", - "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.dmg", - "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.app.zip" - }, - "android": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/android/Navis.apk", - "sha256": "4416770bebe576eba42811990c4e4424f6722dea3a8513e6164dd34fc975b3cf", - "os": "android", - "arch": "universal" - } - } + "version": "4.2.1", + "notes": "4.2.1: исправлены опечатки Румынии (флаг RO), чистая шапка списка серверов, полезные ссылки вместо рекомендаций, убраны мусорные подписи на Защите, большая кнопка ПОДКЛЮЧИТЬСЯ всегда видна, свойства сервера заполнены, без T/V бейджей в списке", + "platform": "windows-amd64", + "os": "windows", + "arch": "amd64", + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe", + "sha256": "c3885d6d0c5b6e542fe1a172650c2f9480afde2b9016ceb1a5699f851761b837", + "mandatory": false, + "platforms": { + "windows-amd64": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe", + "sha256": "c3885d6d0c5b6e542fe1a172650c2f9480afde2b9016ceb1a5699f851761b837", + "os": "windows", + "arch": "amd64" + }, + "darwin-arm64": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis", + "sha256": "01a40b1cce4e2fb3a38eb80f0815c295a00af11e4288f3b9e5c0b58ae336f28d", + "os": "darwin", + "arch": "arm64", + "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.dmg", + "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.app.zip" + }, + "darwin-amd64": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis", + "sha256": "19a066cfc7f7c3a291a220482b28e66a459d091c19c5351818c401b982bc65b3", + "os": "darwin", + "arch": "amd64", + "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.dmg", + "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.app.zip" + }, + "darwin-universal": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis", + "sha256": "d53dba8256fd6f7b62d1ce9bbad741af087ae44fec942bf2782b9c8f4e36cef2", + "os": "darwin", + "arch": "universal", + "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.dmg", + "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.app.zip" + }, + "android": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/android/Navis.apk", + "sha256": "4416770bebe576eba42811990c4e4424f6722dea3a8513e6164dd34fc975b3cf", + "os": "android", + "arch": "universal" + } + } } diff --git a/dist/navis-release/windows/EvilFox.exe b/dist/navis-release/windows/EvilFox.exe index d600464..1037401 100644 Binary files a/dist/navis-release/windows/EvilFox.exe and b/dist/navis-release/windows/EvilFox.exe differ diff --git a/dist/navis-release/windows/Navis.exe b/dist/navis-release/windows/Navis.exe index d600464..1037401 100644 Binary files a/dist/navis-release/windows/Navis.exe and b/dist/navis-release/windows/Navis.exe differ diff --git a/dist/navis-release/windows/evilfox-cli.exe b/dist/navis-release/windows/evilfox-cli.exe index 5d34e93..708dcc2 100644 Binary files a/dist/navis-release/windows/evilfox-cli.exe and b/dist/navis-release/windows/evilfox-cli.exe differ diff --git a/dist/navis-release/windows/vpnclient.exe b/dist/navis-release/windows/vpnclient.exe index 5d34e93..708dcc2 100644 Binary files a/dist/navis-release/windows/vpnclient.exe and b/dist/navis-release/windows/vpnclient.exe differ diff --git a/dist/update.json b/dist/update.json index 0f07365..2569d01 100644 --- a/dist/update.json +++ b/dist/update.json @@ -1,48 +1,48 @@ { - "version": "4.2.0", - "notes": "4.2.0: обновлённые формулировки, Kill Switch, статистика, кнопка подключения", - "platform": "windows-amd64", - "os": "windows", - "arch": "amd64", - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe", - "sha256": "64da79ddf7cd5694bf93c4c110bf0e1281a20651e1e868a960562d5c1a15bd0d", - "mandatory": false, - "platforms": { - "windows-amd64": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe", - "sha256": "64da79ddf7cd5694bf93c4c110bf0e1281a20651e1e868a960562d5c1a15bd0d", - "os": "windows", - "arch": "amd64" - }, - "darwin-arm64": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis", - "sha256": "01a40b1cce4e2fb3a38eb80f0815c295a00af11e4288f3b9e5c0b58ae336f28d", - "os": "darwin", - "arch": "arm64", - "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.dmg", - "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.app.zip" - }, - "darwin-amd64": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis", - "sha256": "19a066cfc7f7c3a291a220482b28e66a459d091c19c5351818c401b982bc65b3", - "os": "darwin", - "arch": "amd64", - "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.dmg", - "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.app.zip" - }, - "darwin-universal": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis", - "sha256": "d53dba8256fd6f7b62d1ce9bbad741af087ae44fec942bf2782b9c8f4e36cef2", - "os": "darwin", - "arch": "universal", - "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.dmg", - "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.app.zip" - }, - "android": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/android/Navis.apk", - "sha256": "4416770bebe576eba42811990c4e4424f6722dea3a8513e6164dd34fc975b3cf", - "os": "android", - "arch": "universal" - } - } + "version": "4.2.1", + "notes": "4.2.1: исправлены опечатки Румынии (флаг RO), чистая шапка списка серверов, полезные ссылки вместо рекомендаций, убраны мусорные подписи на Защите, большая кнопка ПОДКЛЮЧИТЬСЯ всегда видна, свойства сервера заполнены, без T/V бейджей в списке", + "platform": "windows-amd64", + "os": "windows", + "arch": "amd64", + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe", + "sha256": "c3885d6d0c5b6e542fe1a172650c2f9480afde2b9016ceb1a5699f851761b837", + "mandatory": false, + "platforms": { + "windows-amd64": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe", + "sha256": "c3885d6d0c5b6e542fe1a172650c2f9480afde2b9016ceb1a5699f851761b837", + "os": "windows", + "arch": "amd64" + }, + "darwin-arm64": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis", + "sha256": "01a40b1cce4e2fb3a38eb80f0815c295a00af11e4288f3b9e5c0b58ae336f28d", + "os": "darwin", + "arch": "arm64", + "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.dmg", + "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.app.zip" + }, + "darwin-amd64": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis", + "sha256": "19a066cfc7f7c3a291a220482b28e66a459d091c19c5351818c401b982bc65b3", + "os": "darwin", + "arch": "amd64", + "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.dmg", + "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.app.zip" + }, + "darwin-universal": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis", + "sha256": "d53dba8256fd6f7b62d1ce9bbad741af087ae44fec942bf2782b9c8f4e36cef2", + "os": "darwin", + "arch": "universal", + "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.dmg", + "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.app.zip" + }, + "android": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/android/Navis.apk", + "sha256": "4416770bebe576eba42811990c4e4424f6722dea3a8513e6164dd34fc975b3cf", + "os": "android", + "arch": "universal" + } + } } diff --git a/internal/apphost/app.go b/internal/apphost/app.go index 72ae0b8..a27951e 100644 --- a/internal/apphost/app.go +++ b/internal/apphost/app.go @@ -470,13 +470,11 @@ func (a *App) AutoCheckUpdate() { } // allowedLinkHosts are the only hosts the UI may open in the default browser -// (shop button, «Рекомендуемые сервисы» and provider cards in index.html). +// (shop / «Полезные ссылки» / provider cards in index.html). // Competitor links are plain URLs without referral codes (Store policy). var allowedLinkHosts = map[string]bool{ "evilfox.win": true, "t.me": true, - "de4ima.uk": true, - "git.de4ima.uk": true, "nordvpn.com": true, "expressvpn.com": true, "surfshark.com": true, diff --git a/internal/appui/index.html b/internal/appui/index.html index 73de266..670527b 100644 --- a/internal/appui/index.html +++ b/internal/appui/index.html @@ -273,8 +273,9 @@ overflow-y: auto; padding: 18px 20px 8px; } - .page { display: none; max-width: 640px; margin: 0 auto; animation: rise .3s var(--ease) both; } - .page.active { display: block; } + /* Inactive pages must not paint (WebView2 overflow ghosts from hidden siblings). */ + .page { display: none !important; max-width: 640px; margin: 0 auto; } + .page.active { display: block !important; animation: rise .3s var(--ease) both; } @keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } @@ -308,9 +309,12 @@ .update-actions .action { flex: 1 1 auto; min-width: 140px; } /* ==== Home / power ==== */ + /* Sticky so the big connect control never scrolls under the provider list. */ .hero { - position: relative; - overflow: hidden; + position: sticky; + top: 0; + z-index: 6; + overflow: visible; border-radius: var(--radius); border: 1px solid var(--line); background: var(--hero-bg); @@ -318,6 +322,8 @@ margin-bottom: 14px; text-align: center; transition: border-color .25s, box-shadow .25s, background .25s; + min-height: 220px; + box-sizing: border-box; } .hero.on { border-color: rgba(13,138,102,.35); @@ -327,6 +333,8 @@ .power-btn { width: 148px; height: 148px; + min-width: 148px; + min-height: 148px; border-radius: 50%; border: 0; cursor: pointer; @@ -341,6 +349,7 @@ transition: background .25s, color .25s, transform .12s, box-shadow .25s; box-shadow: 0 14px 34px rgba(8, 145, 178, .3); position: relative; + flex-shrink: 0; } .power-btn:hover { transform: translateY(-2px); } .power-btn:active { transform: none; } @@ -1336,7 +1345,7 @@

EvilFox

- 4.2.0 +
-
- Сторонний сервис + Сторонний
NordVPN
Оплата и настройка на сайте провайдера
-
- Сторонний сервис + Сторонний
ExpressVPN
Оплата и настройка на сайте провайдера
-
- Сторонний сервис + Сторонний
Surfshark
Оплата и настройка на сайте провайдера
-
@@ -1478,12 +1483,10 @@
-
Серверы
+
🌍 Серверы
- - - +
@@ -1507,18 +1510,16 @@
Свойства сервера -
-

Адрес сервера:

-
- - -
-
- - -
- +
+
Сервер
+
Пинг
+
Протокол
+
Статус
+ + + +
@@ -1641,11 +1642,11 @@
Расширенные настройки -
+
@@ -1707,7 +1708,7 @@
Не подключается — что делать? -

Проверьте, что конфигурация добавлена и срок её действия не истёк («Мои серверы» → «Информация об аккаунте»). Попробуйте другой сервер (кнопка «Лучший»). Подробности — в журнале на вкладке «Статистика».

+

Проверьте, что конфигурация добавлена и срок её действия не истёк («Мои серверы» → «Информация об аккаунте»). Попробуйте другой сервер (кнопка «Выбрать лучший»). Подробности — в журнале на вкладке «Статистика».

@@ -1725,10 +1726,9 @@ - -
-

Рекомендуемые сервисы

+ +
+

🌐 Полезные ссылки

@@ -1791,8 +1791,8 @@ const btn = $("toggleBtn"); const coreBtn = $("coreBtn"); const saveBtn = $("saveBtn"); - const addBtn = $("addBtn"); - const delBtn = $("delBtn"); + const addBtn = $("addBtn"); // optional — header + removed + const delBtn = $("delBtn"); // optional — header × removed const proxy = $("proxy"); const nameInput = $("name"); const sysproxy = $("sysproxy"); @@ -1843,10 +1843,30 @@ PT: "Португалия", RO: "Румыния", RS: "Сербия", RU: "Россия", SE: "Швеция", SG: "Сингапур", SK: "Словакия", TR: "Турция", UA: "Украина", US: "США", ZA: "ЮАР" }; + // Опечатки / варианты имён стран → ISO (зеркало Go geoflag; длинные сначала). + const COUNTRY_NAME_ALIASES = [ + ["румьиния", "RO"], ["румяння", "RO"], ["румьиня", "RO"], + ["румыния", "RO"], ["румыня", "RO"], ["руминия", "RO"], ["румыниа", "RO"], + ["romania", "RO"], ["bucharest", "RO"], ["бухарест", "RO"] + ]; function countryRu(cc) { const c = String(cc || "").toUpperCase(); return COUNTRY_RU[c] || ""; } + function isoFromNameText(text) { + const lower = String(text || "").toLowerCase(); + if (!lower) return ""; + for (let i = 0; i < COUNTRY_NAME_ALIASES.length; i++) { + if (lower.indexOf(COUNTRY_NAME_ALIASES[i][0]) >= 0) return COUNTRY_NAME_ALIASES[i][1]; + } + // Прямое совпадение канонического русского названия. + const keys = Object.keys(COUNTRY_RU); + for (let i = 0; i < keys.length; i++) { + const ru = COUNTRY_RU[keys[i]].toLowerCase(); + if (ru && lower.indexOf(ru) >= 0) return keys[i]; + } + return ""; + } // ==== SVG-флаги ==== // Windows не умеет эмодзи-флаги: Segoe UI Emoji рисует пары региональных // индикаторов буквами («BE», «LV»), поэтому рисуем настоящие SVG-флаги. @@ -1976,7 +1996,7 @@ if (d.e) b += d.e; return ''; } - // ISO2 страны профиля: country от бэкенда, иначе распознаём эмодзи-флаг. + // ISO2 страны профиля: country от бэкенда, иначе имя/флаг, иначе алиасы опечаток. function profileISO(p) { if (!p) return ""; const c = String(p.country || "").toUpperCase(); @@ -1986,7 +2006,7 @@ const a = rs[0].codePointAt(0) - 0x1f1e6, b = rs[1].codePointAt(0) - 0x1f1e6; if (a >= 0 && a < 26 && b >= 0 && b < 26) return String.fromCharCode(65 + a, 65 + b); } - return ""; + return isoFromNameText(p.name) || isoFromNameText(p.host) || ""; } // Заполнить ячейку флага: SVG для известной страны, буквенный код — фолбэк. function setFlagEl(el, p) { @@ -2084,15 +2104,12 @@ const SHOP_URL = "https://evilfox.win/"; // Текст оффера — правьте здесь (показывается на карточке EvilFox на вкладке «Защита»). // Правила Store: никаких цен и упоминаний оплаты внутри приложения. - const OFFER_TEXT = "🚀 3 дня бесплатно"; - // Список рекомендуемых сервисов — замените ссылки/названия здесь. - // Хост каждой ссылки должен быть разрешён в allowlist openURL - // (internal/apphost/app.go → allowedLinkHosts), иначе ссылка не откроется. + const OFFER_TEXT = "🎁 3 дня бесплатного доступа"; + // Полезные ссылки (Помощь). Хост каждой ссылки — в allowlist openURL + // (internal/apphost/app.go → allowedLinkHosts). const RECOMMENDED_SERVICES = [ - { icon: "🛒", name: "EvilFox Shop", url: "https://evilfox.win/" }, - { icon: "✈️", name: "Telegram", url: "https://t.me/evilfox" }, - { icon: "🖥️", name: "Хостинг de4ima", url: "https://de4ima.uk" }, - { icon: "🦊", name: "Git", url: "https://git.de4ima.uk/Evilfox/navi" } + { icon: "🌐", name: "Сайт", url: "https://evilfox.win/" }, + { icon: "✈️", name: "Поддержка: Telegram", url: "https://t.me/evilfox" } ]; const AUTO_BEST_KEY = "navis.autoBest"; // Оффер на карточке EvilFox рендерим из константы, чтобы правка была в одном месте. @@ -2268,7 +2285,7 @@ async function persistHy2IfNeeded(protocol) { const p = (protocol || "").toLowerCase(); - const link = proxy.value.trim().toLowerCase(); + const link = ((proxy && proxy.value) || "").trim().toLowerCase(); if (p === "hysteria2" || link.startsWith("hysteria2://") || link.startsWith("hy2://")) { await saveHy2(readHy2()); } @@ -2334,12 +2351,13 @@ // Человекочитаемые имена серверов («Чехия — оптимальный сервер», «Германия — сервер 2»). // Реальный host:port показываем только в свойствах сервера и в журнале. + // ISO всегда через profileISO (бэкенд + JS-алиасы опечаток) → countryRu, никогда сырое имя панели. let friendlyMap = {}; function rebuildFriendlyNames() { friendlyMap = {}; const groups = {}; profiles.forEach((p) => { - const key = String(p.country || "").toUpperCase() || "__none__"; + const key = profileISO(p) || "__none__"; (groups[key] = groups[key] || []).push(p); }); Object.keys(groups).forEach((key) => { @@ -2360,13 +2378,19 @@ } else { title = region ? (region + " — сервер " + n) : ("Сервер " + n); } - friendlyMap[p.name] = { title: title, region: region }; + friendlyMap[p.name] = { title: title, region: region, iso: key === "__none__" ? "" : key }; }); }); } function friendlyTitle(name) { const f = friendlyMap[name]; - return (f && f.title) || name || ""; + if (f && f.title) return f.title; + // Фолбэк: не показываем сырые опечатки панели — только каноническое имя по ISO. + const p = profiles.find((x) => x.name === name); + const iso = profileISO(p) || isoFromNameText(name); + const region = countryRu(iso); + if (region) return region; + return "Сервер"; } function fillProfiles(list, active) { @@ -2481,10 +2505,7 @@ const right = document.createElement("div"); right.className = "right"; - // Протокол — крошечная буква-иконка с подсказкой; строчный «vless» - // под каждой строкой не повторяем. - const protoBadge = protoLetterBadge(p.protocol); - if (protoBadge) right.appendChild(protoBadge); + // Протокол — только на карточке активного сервера / в «Свойствах», не в строках списка. const ms = document.createElement("span"); const pr = pingMap[p.name]; if (pr && pr.ok) { @@ -2500,12 +2521,12 @@ ms.title = "пинг не измерен"; } right.appendChild(ms); - // [📊] — проверить скорость только этого сервера. + // [📊] — проверить пинг только этого сервера. const pingOne = document.createElement("button"); pingOne.type = "button"; pingOne.className = "row-ping"; pingOne.textContent = "📊"; - pingOne.title = "Проверить скорость этого сервера"; + pingOne.title = "Проверить пинг этого сервера"; pingOne.addEventListener("click", (e) => { e.stopPropagation(); runPingOne(p.name, pingOne, ms); @@ -2520,20 +2541,6 @@ }); } - const PROTO_LETTER = { - vless: "V", vmess: "V", trojan: "T", hysteria2: "H", hy2: "H", - naive: "N", awg: "A" - }; - function protoLetterBadge(protocol) { - const p = String(protocol || "").toLowerCase(); - if (!p) return null; - const el = document.createElement("span"); - el.className = "proto-badge"; - el.textContent = PROTO_LETTER[p] || p.charAt(0).toUpperCase(); - el.title = "Протокол: " + p; - return el; - } - // Пинг одного сервера по кнопке [📊] в строке. async function runPingOne(name, btnEl, msEl) { if (btnEl.disabled) return; @@ -2689,11 +2696,17 @@ const daysLeft = Math.floor((d.getTime() - Date.now()) / 86400000); const v = $("subExpireVal"); if (daysLeft < 0) { - v.textContent = text + " · срок истёк"; + v.textContent = text + " · Истёк"; v.classList.add("warn"); - } else { - v.textContent = daysLeft <= 14 ? (text + " · осталось " + daysLeft + " дн.") : text; + } else if (daysLeft === 0) { + v.textContent = text + " · Сегодня"; + v.classList.add("warn"); + } else if (daysLeft <= 14) { + v.textContent = text + " · осталось " + daysLeft + " дн."; v.classList.toggle("warn", daysLeft <= 3); + } else { + v.textContent = text; + v.classList.remove("warn"); } } @@ -2713,12 +2726,12 @@ } function renderUpdate(u, version) { - const label = "EvilFox v" + (version || "?"); - verLabel.textContent = label; + // Версия — только в «Помощь»; в сайдбаре бренд без номера. + if (verLabel) verLabel.textContent = "EvilFox"; if (aboutVer) aboutVer.textContent = "Версия " + (version || "?"); - if (badgeVer && version) { - const short = String(version).split("+")[0]; - badgeVer.textContent = short || version; + if (badgeVer) { + badgeVer.hidden = true; + badgeVer.textContent = ""; } if (storePackaged || (u && u.store_managed)) { updateBanner.classList.remove("show"); @@ -2780,14 +2793,14 @@ } const lock = connected || busy; - proxy.disabled = lock; - nameInput.disabled = lock; + if (proxy) proxy.disabled = lock; + if (nameInput) nameInput.disabled = lock; sysproxy.disabled = lock; profile.disabled = lock; - addBtn.disabled = lock; - delBtn.disabled = lock || ((state.profiles || profiles).length <= 1); - saveBtn.disabled = lock; - coreBtn.disabled = busy; + if (addBtn) addBtn.disabled = lock; + if (delBtn) delBtn.disabled = lock || ((state.profiles || profiles).length <= 1); + if (saveBtn) saveBtn.disabled = lock; + if (coreBtn) coreBtn.disabled = busy; pingBtn.disabled = busy; bestBtn.disabled = busy; updCheckBtn.disabled = busy; @@ -2808,8 +2821,8 @@ if (syncForm || (!formHydrated && !dirty)) { const active = (state.profiles || []).find((p) => p.name === profile.value) || {}; - nameInput.value = active.name || state.active_profile || ""; - proxy.value = typeof state.proxy === "string" ? state.proxy : (active.proxy || ""); + if (nameInput) nameInput.value = active.name || state.active_profile || ""; + if (proxy) proxy.value = typeof state.proxy === "string" ? state.proxy : (active.proxy || ""); if (typeof state.system_proxy === "boolean") sysproxy.checked = state.system_proxy; fillHy2(state.hy2); formHydrated = true; @@ -2826,10 +2839,13 @@ const hy2 = (label === "hysteria2" || label === "hy2"); if ($("hy2Box")) $("hy2Box").style.display = hy2 ? "" : "none"; - // Технические адреса прокси — только в «Настройки → Расширенные» и на «Статистике». + // Параметры прокси — только когда подключены (без пустого «появится после…»). const advHttp = $("advHttp"), advSocks = $("advSocks"), logsProxyLine = $("logsProxyLine"); - if (advHttp) advHttp.textContent = state.http_proxy || "появится после подключения"; - if (advSocks) advSocks.textContent = state.socks_proxy || "появится после подключения"; + const proxyParamsBlock = $("proxyParamsBlock"); + const hasProxyParams = !!(state.http_proxy || state.socks_proxy); + if (proxyParamsBlock) proxyParamsBlock.hidden = !hasProxyParams; + if (advHttp) advHttp.textContent = state.http_proxy || "—"; + if (advSocks) advSocks.textContent = state.socks_proxy || "—"; if (logsProxyLine) { const parts = []; if (state.http_proxy) parts.push("HTTP " + state.http_proxy); @@ -2838,7 +2854,7 @@ logsProxyLine.textContent = parts.length ? ("Прокси: " + parts.join(" · ")) : ""; } renderProvConnect(); - renderProfHost(); + renderProfProps(); // Нижняя полоса статуса — только для важных сообщений; постоянного // «Готово к подключению» больше нет (статус виден на большой кнопке). @@ -2878,12 +2894,30 @@ } } - // Реальный host:port выбранного сервера — в «Свойствах сервера» (муторные детали). - function renderProfHost() { - const el = $("profHost"); - if (!el) return; + // Свойства выбранного сервера: имя / пинг / протокол / статус. + function renderProfProps() { const p = profiles.find((x) => x.name === profile.value); - el.textContent = (p && p.host) ? p.host : "—"; + const nameEl = $("propName"), pingEl = $("propPing"), protoEl = $("propProto"), stEl = $("propStatus"); + if (!nameEl) return; + if (!p) { + nameEl.textContent = "—"; + if (pingEl) pingEl.textContent = "—"; + if (protoEl) protoEl.textContent = "—"; + if (stEl) stEl.textContent = "—"; + return; + } + nameEl.textContent = friendlyTitle(p.name); + const pr = pingMap[p.name]; + if (pingEl) { + if (pr && pr.ok) pingEl.textContent = pr.ms + " ms"; + else if (pr && pr.error) pingEl.textContent = "недоступен"; + else pingEl.textContent = "не измерен"; + } + if (protoEl) { + const proto = String(p.protocol || "—"); + protoEl.textContent = proto.charAt(0).toUpperCase() + proto.slice(1); + } + if (stEl) stEl.textContent = connected ? "🟢 Активен" : "⚪ Не активен"; } async function refresh(opts) { @@ -2949,23 +2983,31 @@ await runBest(true); } else { setMeta("Подключение…"); - await saveProfile(nameInput.value.trim() || profile.value, proxy.value.trim(), !!sysproxy.checked); - await persistHy2IfNeeded(); + // Профиль уже выбран; скрытые поля name/proxy гидратируются из state. + // Не перезаписываем proxy пустой строкой, если форма ещё не заполнена. + const n = (nameInput && nameInput.value.trim()) || profile.value; + const px = (proxy && proxy.value.trim()) || ""; + if (n && px) { + await saveProfile(n, px, !!sysproxy.checked); + await persistHy2IfNeeded(); + } await connect(); setMeta("Защищённое соединение установлено", "ok"); } } catch (e) { setMeta(String(e), "err"); } })); - saveBtn.addEventListener("click", () => withBusy(async () => { + if (saveBtn) saveBtn.addEventListener("click", () => withBusy(async () => { try { - await saveProfile(nameInput.value.trim() || profile.value, proxy.value.trim(), !!sysproxy.checked); + const n = (nameInput && nameInput.value.trim()) || profile.value; + const px = (proxy && proxy.value.trim()) || ""; + await saveProfile(n, px, !!sysproxy.checked); await persistHy2IfNeeded(); setMeta("Профиль сохранён", "ok"); } catch (e) { setMeta(String(e), "err"); } })); - coreBtn.addEventListener("click", () => withBusy(async () => { + if (coreBtn) coreBtn.addEventListener("click", () => withBusy(async () => { try { setMeta("Скачивание official cores…"); const path = await installCore(); @@ -2973,7 +3015,7 @@ } catch (e) { setMeta(String(e), "err"); } })); - delBtn.addEventListener("click", () => withBusy(async () => { + if (delBtn) delBtn.addEventListener("click", () => withBusy(async () => { try { if (!confirm("Удалить сервер «" + friendlyTitle(profile.value) + "»?")) return; await deleteProfile(profile.value); @@ -2983,7 +3025,7 @@ } catch (e) { setMeta(String(e), "err"); } })); - addBtn.addEventListener("click", () => { + if (addBtn) addBtn.addEventListener("click", () => { $("newName").value = ""; $("newProxy").value = ""; modal.classList.add("open"); @@ -3022,7 +3064,7 @@ shopBtn.addEventListener("click", openShop); shopLink.addEventListener("click", openShop); - // «Рекомендуемые сервисы» — рендер из RECOMMENDED_SERVICES (см. константу выше). + // «Рекомендуемые сервисы» → «Полезные ссылки» (см. RECOMMENDED_SERVICES). (function renderRecommendedServices() { const grid = $("svcGrid"); if (!grid) return; @@ -3097,60 +3139,6 @@ if (!connected && !busy) btn.click(); }); - function setPingBtnResult(b, ms) { - const val = b.querySelector(".val"); - if (val) val.textContent = ms + " мс"; - b.classList.toggle("good", ms < 40); - b.classList.toggle("mid", ms >= 40); - } - - // «Проверить скорость» на карточке EvilFox — реальный пинг до наших серверов. - const provPingOurs = $("provPingOurs"); - if (provPingOurs) provPingOurs.addEventListener("click", async () => { - if (provPingOurs.disabled) return; - const val = provPingOurs.querySelector(".val"); - provPingOurs.disabled = true; - if (val) val.textContent = "измерение…"; - try { - const rows = await pingServers(); - rememberPings(rows); - renderServerList(profile.value); - renderCurServer(profile.value); - const oks = (rows || []).filter((r) => r.ok).map((r) => r.ms); - if (oks.length) { - setPingBtnResult(provPingOurs, Math.min.apply(null, oks)); - } else if (val) { - val.textContent = "серверы недоступны"; - } - } catch (_) { - if (val) val.textContent = "не удалось измерить"; - } finally { - provPingOurs.disabled = false; - } - }); - - // «Проверить скорость» у сторонних провайдеров — оценка задержки до их - // публичных серверов от текущего местоположения (ориентировочно). - document.querySelectorAll(".prov-ping[data-base]").forEach((b) => { - b.addEventListener("click", () => { - if (b.disabled) return; - const val = b.querySelector(".val"); - b.disabled = true; - if (val) val.textContent = "измерение…"; - const base = parseInt(b.dataset.base, 10) || 45; - setTimeout(() => { - let ms = base + Math.round(Math.random() * 12 - 5); - const ourBest = Object.keys(pingMap) - .map((k) => pingMap[k]) - .filter((p) => p && p.ok) - .reduce((m, p) => Math.min(m, p.ms), Infinity); - if (isFinite(ourBest) && ms <= ourBest + 15) ms = ourBest + 18 + Math.round(Math.random() * 10); - setPingBtnResult(b, ms); - b.disabled = false; - }, 1500 + Math.random() * 900); - }); - }); - subBtn.addEventListener("click", () => withBusy(async () => { try { await runImportSubscription(); diff --git a/internal/geoflag/geoflag.go b/internal/geoflag/geoflag.go index 0bce449..c555f86 100644 --- a/internal/geoflag/geoflag.go +++ b/internal/geoflag/geoflag.go @@ -141,7 +141,9 @@ var countryNames = []nameCode{ {"сша", "US"}, {"америка", "US"}, // Частые опечатки в именах серверов на панелях (наблюдались в проде). - {"румьиня", "RO"}, {"румыня", "RO"}, {"руминия", "RO"}, + // Longer stems first (Contains): «румьиния» before «румьиня». + {"румьиния", "RO"}, {"румяння", "RO"}, {"румьиня", "RO"}, + {"румыня", "RO"}, {"руминия", "RO"}, {"румыниа", "RO"}, // City → country hints (EN + RU). {"frankfurt", "DE"}, {"amsterdam", "NL"}, {"london", "GB"}, diff --git a/internal/geoflag/geoflag_test.go b/internal/geoflag/geoflag_test.go index 0ac86a9..93130b7 100644 --- a/internal/geoflag/geoflag_test.go +++ b/internal/geoflag/geoflag_test.go @@ -28,6 +28,12 @@ func TestFromText(t *testing.T) { // Panel-side typos seen in production. {"Румьиня", "RO"}, {"Румыня-2", "RO"}, + {"Румяння", "RO"}, + {"Румяння — оптимальный", "RO"}, + {"Румьиния", "RO"}, + {"Румьиния Fast", "RO"}, + {"румыниа", "RO"}, + {"Румыния", "RO"}, } for _, c := range cases { cc, emoji := FromText(c.in) diff --git a/internal/update/update.go b/internal/update/update.go index 6ef1f01..1f3a120 100644 --- a/internal/update/update.go +++ b/internal/update/update.go @@ -18,7 +18,7 @@ import ( // CurrentVersion is the product/semver used for update eligibility (feed "version"). // Keep major.minor.patch only — no build suffix here. -const CurrentVersion = "4.2.0" +const CurrentVersion = "4.2.1" // BuildNumber is the monotonic build within CurrentVersion (Windows FileVersion 4th part, // macOS CFBundleVersion suffix, Android versionCode low digits). Bump on every release build. diff --git a/packaging/msix/AppxManifest.xml b/packaging/msix/AppxManifest.xml index 245a47b..5373394 100644 --- a/packaging/msix/AppxManifest.xml +++ b/packaging/msix/AppxManifest.xml @@ -33,7 +33,7 @@ diff --git a/scripts/pack-msix.ps1 b/scripts/pack-msix.ps1 index 219e393..1a8c28e 100644 --- a/scripts/pack-msix.ps1 +++ b/scripts/pack-msix.ps1 @@ -15,7 +15,7 @@ param( [string]$ExePath = "", [string]$OutDir = "", - [string]$Version = "4.2.0.1", + [string]$Version = "4.2.1.1", [string]$Name = "EvilFox.EvilFox", [string]$Publisher = "CN=EvilFox", [string]$DisplayName = "EvilFox", @@ -82,7 +82,7 @@ $manifestText = Get-Content -LiteralPath $ManifestSrc -Raw -Encoding UTF8 # Prefer exact placeholder swaps so we never touch . $manifestText = $manifestText.Replace('Name="EvilFox.EvilFox"', "Name=`"$Name`"") $manifestText = $manifestText.Replace('Publisher="CN=EvilFox"', "Publisher=`"$Publisher`"") -$manifestText = $manifestText.Replace('Version="4.2.0.1"', "Version=`"$Version`"") +$manifestText = $manifestText.Replace('Version="4.2.1.1"', "Version=`"$Version`"") $manifestText = $manifestText.Replace('EvilFox', "$DisplayName") $manifestText = $manifestText.Replace('EvilFox', "$PublisherDisplayName") $manifestText = $manifestText.Replace('DisplayName="EvilFox"', "DisplayName=`"$DisplayName`"") diff --git a/server/update.json b/server/update.json index 0f07365..2569d01 100644 --- a/server/update.json +++ b/server/update.json @@ -1,48 +1,48 @@ { - "version": "4.2.0", - "notes": "4.2.0: обновлённые формулировки, Kill Switch, статистика, кнопка подключения", - "platform": "windows-amd64", - "os": "windows", - "arch": "amd64", - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe", - "sha256": "64da79ddf7cd5694bf93c4c110bf0e1281a20651e1e868a960562d5c1a15bd0d", - "mandatory": false, - "platforms": { - "windows-amd64": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe", - "sha256": "64da79ddf7cd5694bf93c4c110bf0e1281a20651e1e868a960562d5c1a15bd0d", - "os": "windows", - "arch": "amd64" - }, - "darwin-arm64": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis", - "sha256": "01a40b1cce4e2fb3a38eb80f0815c295a00af11e4288f3b9e5c0b58ae336f28d", - "os": "darwin", - "arch": "arm64", - "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.dmg", - "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.app.zip" - }, - "darwin-amd64": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis", - "sha256": "19a066cfc7f7c3a291a220482b28e66a459d091c19c5351818c401b982bc65b3", - "os": "darwin", - "arch": "amd64", - "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.dmg", - "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.app.zip" - }, - "darwin-universal": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis", - "sha256": "d53dba8256fd6f7b62d1ce9bbad741af087ae44fec942bf2782b9c8f4e36cef2", - "os": "darwin", - "arch": "universal", - "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.dmg", - "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.app.zip" - }, - "android": { - "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/android/Navis.apk", - "sha256": "4416770bebe576eba42811990c4e4424f6722dea3a8513e6164dd34fc975b3cf", - "os": "android", - "arch": "universal" - } - } + "version": "4.2.1", + "notes": "4.2.1: исправлены опечатки Румынии (флаг RO), чистая шапка списка серверов, полезные ссылки вместо рекомендаций, убраны мусорные подписи на Защите, большая кнопка ПОДКЛЮЧИТЬСЯ всегда видна, свойства сервера заполнены, без T/V бейджей в списке", + "platform": "windows-amd64", + "os": "windows", + "arch": "amd64", + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe", + "sha256": "c3885d6d0c5b6e542fe1a172650c2f9480afde2b9016ceb1a5699f851761b837", + "mandatory": false, + "platforms": { + "windows-amd64": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe", + "sha256": "c3885d6d0c5b6e542fe1a172650c2f9480afde2b9016ceb1a5699f851761b837", + "os": "windows", + "arch": "amd64" + }, + "darwin-arm64": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis", + "sha256": "01a40b1cce4e2fb3a38eb80f0815c295a00af11e4288f3b9e5c0b58ae336f28d", + "os": "darwin", + "arch": "arm64", + "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.dmg", + "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/arm64/Navis.app.zip" + }, + "darwin-amd64": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis", + "sha256": "19a066cfc7f7c3a291a220482b28e66a459d091c19c5351818c401b982bc65b3", + "os": "darwin", + "arch": "amd64", + "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.dmg", + "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/amd64/Navis.app.zip" + }, + "darwin-universal": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis", + "sha256": "d53dba8256fd6f7b62d1ce9bbad741af087ae44fec942bf2782b9c8f4e36cef2", + "os": "darwin", + "arch": "universal", + "dmg_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.dmg", + "zip_url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/macos/universal/Navis.app.zip" + }, + "android": { + "url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/android/Navis.apk", + "sha256": "4416770bebe576eba42811990c4e4424f6722dea3a8513e6164dd34fc975b3cf", + "os": "android", + "arch": "universal" + } + } } diff --git a/versioninfo.json b/versioninfo.json index 6261454..ea4b773 100644 --- a/versioninfo.json +++ b/versioninfo.json @@ -1,7 +1,7 @@ { "FixedFileInfo": { - "FileVersion": { "Major": 4, "Minor": 2, "Patch": 0, "Build": 1 }, - "ProductVersion": { "Major": 4, "Minor": 2, "Patch": 0, "Build": 1 }, + "FileVersion": { "Major": 4, "Minor": 2, "Patch": 1, "Build": 1 }, + "ProductVersion": { "Major": 4, "Minor": 2, "Patch": 1, "Build": 1 }, "FileFlagsMask": "3f", "FileFlags": "00", "FileOS": "40004", @@ -11,12 +11,12 @@ "StringFileInfo": { "CompanyName": "EvilFox", "FileDescription": "EvilFox — VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)", - "FileVersion": "4.2.0.1", + "FileVersion": "4.2.1.1", "InternalName": "EvilFox", "LegalCopyright": "Copyright (c) EvilFox", "OriginalFilename": "EvilFox.exe", "ProductName": "EvilFox", - "ProductVersion": "4.2.0.1", + "ProductVersion": "4.2.1.1", "Comments": "Open-source VPN/proxy client. https://evilfox.win/" }, "VarFileInfo": {