universal-runtime: replace status badge with centered overlay Подготовка приложения...
This commit is contained in:
@@ -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,8 +74,9 @@ 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;
|
||||||
|
|
||||||
let rfb = null;
|
let rfb = null;
|
||||||
let connected = false;
|
let connected = false;
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user