From a88930b7b197d2df93293bddaab4a18d3085d852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Fri, 14 Aug 2026 00:14:32 +0300 Subject: [PATCH] Show actionable warning when card server fetch fails; fix connect-button locale - Detect likely browser PNA/local-network block via origin-vs-target heuristic - Show warning mentioning both causes (server down + browser block) - Fix 'Connect' button resetting to English after failed connect (t('Connect')) - Fix 'Connection failed:' translation never matching (trailing-space key) --- index.html | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index f8d3bb6..7a8bfe1 100644 --- a/index.html +++ b/index.html @@ -2648,6 +2648,21 @@ async function pysimFetch(path, body) { return await res.json(); } +function pysimTargetSpace() { + let h; + try { h = new URL(pysimBase).hostname.toLowerCase(); } catch (e) { return 'public'; } + if (h === 'localhost' || h === '[::1]' || h === '::1' || /^127\./.test(h)) return 'loopback'; + if (/^10\./.test(h) || /^192\.168\./.test(h) || /^172\.(1[6-9]|2\d|3[01])\./.test(h)) return 'private'; + return 'public'; +} + +function pysimIsLikelyBlocked() { + const pagePublic = location.protocol === 'https:' + || !(location.hostname === 'localhost' || /^127\./.test(location.hostname)); + const s = pysimTargetSpace(); + return pagePublic && (s === 'loopback' || s === 'private'); +} + function pysimSetConnected(state) { const img = document.getElementById('pysim-status-img'); if (state === 'spin') { @@ -2694,7 +2709,7 @@ async function pysimRefresh() { return true; } catch (e) { pysimSetConnected(false); - statusEl.textContent = t('Connection failed: ') + ' ' + e.message; + statusEl.textContent = t('Connection failed:') + ' ' + e.message; return false; } } @@ -2715,7 +2730,7 @@ async function pysimConnect() { const sv = (ver.version || '').split('.').map(Number); if (sv[0] < 1 || (sv[0] === 1 && sv[1] < 0)) { statusEl.innerHTML = 'Server version ' + esc(ver.version) + ' is outdated. Please update pysim-otaman-server.'; - btn.textContent = 'Connect'; + btn.textContent = t('Connect'); btn.disabled = false; return; } @@ -2746,8 +2761,12 @@ async function pysimConnect() { stkCheckMenu(); } catch (e) { pysimSetConnected(false); - statusEl.innerHTML = '' + t('Connection failed: ') + ' ' + esc(e.message) + ''; - btn.textContent = 'Connect'; + const hint = t(pysimIsLikelyBlocked() ? 'pysim-blocked-hint' : 'pysim-unreachable-hint') + .replace('{url}', esc(pysimBase)); + statusEl.innerHTML = + '' + t('Connection failed:') + ' ' + esc(e.message) + '' + + '
' + hint + ''; + btn.textContent = t('Connect'); } btn.disabled = false; } @@ -4698,6 +4717,8 @@ const LANG_RU = { 'Checking card...': 'Проверка карты...', 'Connected': 'Подключено', 'Connection failed:': 'Ошибка подключения:', + 'pysim-blocked-hint': 'Браузер, вероятно, заблокировал доступ к локальному серверу карт. Разрешите доступ к локальной сети для этого сайта (Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → Разрешить). Также проверьте, что pysim-otaman-server запущен по адресу {url}.', + 'pysim-unreachable-hint': 'Не удалось подключиться к серверу карт. Проверьте, что pysim-otaman-server запущен и адрес указан верно ({url}). Если эта страница открыта по HTTPS, также разрешите доступ к локальной сети в браузере (Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → Разрешить).', 'No card detected. Insert card and click Equip card button': 'Карта не обнаружена. Вставьте карту и нажмите Подключить карту', 'Checking...': 'Проверка...', 'Resetting...': 'Сброс...',