universal-runtime: replace status badge with centered overlay Подготовка приложения...

This commit is contained in:
2026-07-20 15:45:50 +00:00
parent f61953f32a
commit 19a9764d8e
+54 -55
View File
@@ -20,25 +20,26 @@ cat > /opt/portal/index.html <<'HTML'
<title>Universal Session</title> <title>Universal Session</title>
<style> <style>
html,body,#screen{margin:0;height:100%;background:#111} html,body,#screen{margin:0;height:100%;background:#111}
.status{ #co{
position:fixed; position:fixed;inset:0;z-index:50;background:rgba(8,14,24,.95);
left:12px; display:flex;align-items:center;justify-content:center;
top:12px; transition:opacity .5s;
z-index:50;
padding:8px 10px;
border-radius:8px;
background:rgba(16,22,32,.86);
border:1px solid rgba(255,255,255,.18);
color:#dce8f5;
font:600 13px/1.25 sans-serif;
max-width:min(92vw,560px);
} }
.status.error{ #co.hidden{opacity:0;pointer-events:none}
background:rgba(85,20,20,.9); .co-inner{display:flex;flex-direction:column;align-items:center;gap:20px;padding:40px}
border-color:rgba(255,130,130,.36); .co-ring{
color:#ffe3e3; width:64px;height:64px;border-radius:50%;
border:4px solid rgba(220,232,245,.12);
border-top-color:#4a9de0;
animation:spin .9s linear infinite;
}
.co-title{color:#dce8f5;font:600 1.1rem/1.4 sans-serif;text-align:center;letter-spacing:.01em}
@keyframes spin{to{transform:rotate(360deg)}}
#status{
position:fixed;left:12px;top:12px;z-index:60;padding:8px 12px;border-radius:8px;
background:rgba(85,20,20,.9);border:1px solid rgba(255,130,130,.36);
color:#ffe3e3;font:600 13px/1.25 sans-serif;max-width:min(92vw,560px);display:none;
} }
.status.hidden{display:none}
.nav-panel{ .nav-panel{
position:fixed;left:16px;top:64px;z-index:99;display:flex;gap:10px; position:fixed;left:16px;top:64px;z-index:99;display:flex;gap:10px;
background:linear-gradient(180deg,rgba(15,24,36,.78),rgba(9,14,22,.86));border:1px solid rgba(255,255,255,.22);backdrop-filter: blur(5px); background:linear-gradient(180deg,rgba(15,24,36,.78),rgba(9,14,22,.86));border:1px solid rgba(255,255,255,.22);backdrop-filter: blur(5px);
@@ -56,7 +57,13 @@ cat > /opt/portal/index.html <<'HTML'
</head> </head>
<body> <body>
<div id="screen"></div> <div id="screen"></div>
<div id="status" class="status">Подключение к слоту...</div> <div id="co">
<div class="co-inner">
<div class="co-ring"></div>
<div class="co-title">Подготовка приложения...</div>
</div>
</div>
<div id="status"></div>
<div class="nav-panel"> <div class="nav-panel">
<button class="nav-btn" id="btn-back" type="button" title="Назад">←</button> <button class="nav-btn" id="btn-back" type="button" title="Назад">←</button>
<button class="nav-btn" id="btn-home" type="button" title="Главная">⌂</button> <button class="nav-btn" id="btn-home" type="button" title="Главная">⌂</button>
@@ -67,6 +74,7 @@ cat > /opt/portal/index.html <<'HTML'
const basePath = location.pathname.replace(/\/+$/, ''); const basePath = location.pathname.replace(/\/+$/, '');
const wsUrl = (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + basePath + '/websockify'; const wsUrl = (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + basePath + '/websockify';
const statusEl = document.getElementById('status'); const statusEl = document.getElementById('status');
const coEl = document.getElementById('co');
const XK_ALT_L = 0xffe9; const XK_ALT_L = 0xffe9;
const XK_LEFT = 0xff51; const XK_LEFT = 0xff51;
@@ -79,41 +87,40 @@ cat > /opt/portal/index.html <<'HTML'
const RECONNECT_DELAYS_MS = [1000, 2000, 3000, 5000, 8000]; const RECONNECT_DELAYS_MS = [1000, 2000, 3000, 5000, 8000];
let manualDisconnect = false; let manualDisconnect = false;
function showStatus(text, isError = false) { function showOverlay() {
statusEl.textContent = text; coEl.style.opacity = '1';
statusEl.classList.toggle('error', !!isError); coEl.style.display = 'flex';
statusEl.classList.remove('hidden'); coEl.classList.remove('hidden');
statusEl.style.display = 'none';
} }
function hideStatus() { function hideOverlay() {
statusEl.classList.add('hidden'); coEl.classList.add('hidden');
setTimeout(() => { coEl.style.display = 'none'; }, 520);
}
function showError(text) {
hideOverlay();
statusEl.textContent = text;
statusEl.style.display = 'block';
} }
function clearConnectTimer() { function clearConnectTimer() {
if (connectTimer) { if (connectTimer) { clearTimeout(connectTimer); connectTimer = null; }
clearTimeout(connectTimer);
connectTimer = null;
}
} }
function clearReconnectTimer() { function clearReconnectTimer() {
if (reconnectTimer) { if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
} }
function reconnectDelay(attemptNumber) { function reconnectDelay(n) {
const idx = Math.min(Math.max(attemptNumber - 1, 0), RECONNECT_DELAYS_MS.length - 1); return RECONNECT_DELAYS_MS[Math.min(Math.max(n - 1, 0), RECONNECT_DELAYS_MS.length - 1)];
return RECONNECT_DELAYS_MS[idx];
} }
function scheduleConnectTimeout() { function scheduleConnectTimeout() {
clearConnectTimer(); clearConnectTimer();
connectTimer = setTimeout(() => { connectTimer = setTimeout(() => {
if (!connected && !manualDisconnect) { if (!connected && !manualDisconnect) scheduleReconnect('Нет подключения. Пробуем переподключиться...');
scheduleReconnect('Нет подключения к экрану слота. Пробуем переподключиться...');
}
}, 8000); }, 8000);
} }
@@ -121,51 +128,43 @@ cat > /opt/portal/index.html <<'HTML'
if (manualDisconnect) return; if (manualDisconnect) return;
clearConnectTimer(); clearConnectTimer();
clearReconnectTimer(); clearReconnectTimer();
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) { if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
showStatus('Соединение со слотом потеряно. Переподключение не удалось. Откройте сервис заново.', true); showError('Соединение потеряно. Переподключение не удалось. Откройте сервис заново.');
return; return;
} }
const nextAttempt = reconnectAttempts + 1; const nextAttempt = reconnectAttempts + 1;
const delayMs = reconnectDelay(nextAttempt); const delayMs = reconnectDelay(nextAttempt);
const delaySec = Math.ceil(delayMs / 1000); showError(\`\${reasonText} Повтор \${nextAttempt}/\${MAX_RECONNECT_ATTEMPTS} через \${Math.ceil(delayMs/1000)} сек.\`);
showStatus(`${reasonText} Повтор ${nextAttempt}/${MAX_RECONNECT_ATTEMPTS} через ${delaySec} сек.`, true);
reconnectTimer = setTimeout(() => { reconnectTimer = setTimeout(() => {
reconnectAttempts = nextAttempt; reconnectAttempts = nextAttempt;
connectRfb('Переподключение к слоту...'); connectRfb();
}, delayMs); }, delayMs);
} }
function attachRfb() { function attachRfb() {
if (rfb) { if (rfb) { try { rfb.disconnect(); } catch (e) {} }
try { rfb.disconnect(); } catch (e) {}
}
rfb = new RFB(document.getElementById('screen'), wsUrl); rfb = new RFB(document.getElementById('screen'), wsUrl);
rfb.viewOnly = false; rfb.viewOnly = false;
rfb.scaleViewport = true; rfb.scaleViewport = true;
rfb.resizeSession = true; rfb.resizeSession = true;
rfb.addEventListener('connect', () => { rfb.addEventListener('connect', () => {
connected = true; connected = true;
reconnectAttempts = 0; reconnectAttempts = 0;
clearConnectTimer(); clearConnectTimer();
clearReconnectTimer(); clearReconnectTimer();
hideStatus(); hideOverlay();
}); });
rfb.addEventListener('disconnect', () => { rfb.addEventListener('disconnect', () => {
connected = false; connected = false;
if (manualDisconnect) return; if (manualDisconnect) return;
scheduleReconnect('Соединение со слотом потеряно.'); scheduleReconnect('Соединение потеряно.');
}); });
} }
function connectRfb(statusText) { function connectRfb() {
if (manualDisconnect) return; if (manualDisconnect) return;
connected = false; connected = false;
showStatus(statusText || 'Подключение к слоту...'); showOverlay();
attachRfb(); attachRfb();
scheduleConnectTimeout(); scheduleConnectTimeout();
} }
@@ -291,7 +290,7 @@ cat > /opt/portal/index.html <<'HTML'
}); });
})(); })();
connectRfb('Подключение к слоту...'); connectRfb();
</script> </script>
</body> </body>
</html> </html>