mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 09:35:39 +00:00
4eed88673c
- deployServerBinary: use rename(2) for atomic replace, fixes ETXTBSY when target Go binary is busy (Linux kernel handles inode swap). Falls back to copyFileSync on cross-device rename or non-Linux. Windows: rename target out of the way first, then move new in. - settings.js: mark 'server' phase as error when build succeeded but deploy failed (was incorrectly marking 'done' from build alone). - settings.js: completion modal now shows error title, error message and pre-formatted stderr when serverDeploy.success === false. - i18n: added complete_with_errors, modal_done_with_errors_title in en/pl.
190 lines
10 KiB
HTML
190 lines
10 KiB
HTML
<%- include('layouts/viewer', {
|
|
title: deviceId,
|
|
pageScripts: ['cdap-desktop', 'cdap-audio'],
|
|
body: `
|
|
<style>
|
|
.bd-remote-wrap { position: fixed; inset: 0; display: flex; flex-direction: column; background: radial-gradient(circle at top, #18243a 0%, #0d1117 42%, #070b12 100%); color: #e6edf3; }
|
|
.bd-remote-bar { display: flex; align-items: center; gap: 12px; padding: 10px 16px; border-bottom: 1px solid rgba(255,255,255,.08); background: rgba(10,16,27,.8); backdrop-filter: blur(16px); }
|
|
.bd-remote-title { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
.bd-remote-title .material-icons { color: #58a6ff; }
|
|
.bd-remote-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.bd-remote-id { color: #8b949e; font-size: 12px; }
|
|
.bd-remote-pill { padding: 4px 10px; border-radius: 999px; background: rgba(88,166,255,.16); color: #c9d1d9; font-size: 12px; }
|
|
.bd-remote-grow { flex: 1; }
|
|
.bd-remote-actions { display: flex; align-items: center; gap: 8px; }
|
|
.bd-remote-actions button,
|
|
.bd-remote-actions a { display: inline-flex; align-items: center; gap: 6px; border: 1px solid rgba(255,255,255,.12); background: rgba(255,255,255,.06); color: #e6edf3; border-radius: 10px; padding: 8px 12px; font-size: 13px; text-decoration: none; cursor: pointer; }
|
|
.bd-remote-actions button:hover,
|
|
.bd-remote-actions a:hover { background: rgba(255,255,255,.12); }
|
|
.bd-remote-actions button.recording { background: rgba(248,81,73,.18); border-color: rgba(248,81,73,.45); color: #ffa198; }
|
|
.bd-remote-actions button.active { background: rgba(88,166,255,.22); border-color: rgba(88,166,255,.55); color: #79b8ff; }
|
|
.bd-remote-stage { flex: 1; padding: 12px; min-height: 0; }
|
|
.bd-remote-widget { position: relative; width: 100%; height: 100%; display: flex; flex-direction: column; border-radius: 18px; overflow: hidden; background: rgba(255,255,255,.03); border: 1px solid rgba(255,255,255,.08); box-shadow: 0 28px 80px rgba(0,0,0,.45); }
|
|
.bd-remote-widget .cdap-desktop-toolbar { display: flex; align-items: center; gap: 10px; min-height: 52px; padding: 0 16px; background: rgba(7,11,18,.92); border-bottom: 1px solid rgba(255,255,255,.06); }
|
|
.bd-remote-widget .cdap-desktop-canvas-wrap { position: relative; flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; background: #020409; }
|
|
.bd-remote-widget .cdap-desktop-canvas { max-width: 100%; max-height: 100%; width: 100%; height: 100%; object-fit: contain; outline: none; cursor: default; }
|
|
.bd-remote-widget .cdap-desktop-overlay { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background: linear-gradient(180deg, rgba(2,4,9,.45), rgba(2,4,9,.82)); color: #c9d1d9; text-align: center; }
|
|
.bd-remote-widget .cdap-desktop-overlay .material-icons { font-size: 44px; color: #58a6ff; }
|
|
.bd-remote-widget .cdap-desktop-connect { position: absolute; right: 20px; bottom: 20px; z-index: 20; }
|
|
.bd-remote-widget .cdap-desktop-connect.hidden,
|
|
.bd-remote-widget .cdap-desktop-overlay.hidden,
|
|
.bd-remote-widget .cdap-desktop-clipboard-indicator.hidden,
|
|
.bd-remote-widget .hidden { display: none !important; }
|
|
.bd-remote-widget .cdap-desktop-clipboard-indicator { margin-left: auto; font-size: 12px; padding: 4px 10px; border-radius: 999px; background: rgba(88,166,255,.14); color: #c9d1d9; }
|
|
.bd-remote-widget .cdap-monitor-selector { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; color: #c9d1d9; }
|
|
.bd-remote-widget .cdap-monitor-selector select { background: rgba(255,255,255,.06); color: #e6edf3; border: 1px solid rgba(255,255,255,.12); border-radius: 8px; padding: 6px 10px; }
|
|
.bd-remote-hint { font-size: 12px; color: #8b949e; }
|
|
@media (max-width: 900px) {
|
|
.bd-remote-bar { flex-wrap: wrap; }
|
|
.bd-remote-grow { display: none; }
|
|
.bd-remote-actions { width: 100%; justify-content: flex-end; }
|
|
}
|
|
</style>
|
|
|
|
<div class="bd-remote-wrap">
|
|
<div class="bd-remote-bar">
|
|
<div class="bd-remote-title">
|
|
<span class="material-icons">desktop_windows</span>
|
|
<div>
|
|
<div class="bd-remote-name">${deviceName || deviceId}</div>
|
|
<div class="bd-remote-id">${deviceId}</div>
|
|
</div>
|
|
</div>
|
|
<span class="bd-remote-pill">CDAP Desktop</span>
|
|
<span class="bd-remote-hint">Click inside the canvas to capture keyboard and mouse input.</span>
|
|
<span class="bd-remote-grow"></span>
|
|
<div class="bd-remote-actions">
|
|
<button type="button" id="bd-remote-paste" title="Paste clipboard text into the remote session"><span class="material-icons">content_paste</span>Paste</button>
|
|
<button type="button" id="bd-remote-audio" title="Toggle remote audio"><span class="material-icons">volume_up</span>Audio</button>
|
|
<button type="button" id="bd-remote-record" title="Record session to WebM"><span class="material-icons">fiber_manual_record</span>Record</button>
|
|
<button type="button" id="bd-remote-fullscreen" title="Fullscreen + capture system shortcuts"><span class="material-icons">fullscreen</span>Fullscreen</button>
|
|
<button type="button" id="bd-remote-reconnect"><span class="material-icons">refresh</span>Reconnect</button>
|
|
<a href="/devices"><span class="material-icons">arrow_back</span>Back</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bd-remote-stage">
|
|
<div class="cdap-desktop-widget bd-remote-widget" id="wval-remote-full">
|
|
<div class="cdap-desktop-toolbar">
|
|
<span class="cdap-desktop-clipboard-indicator hidden"></span>
|
|
<span class="bd-remote-hint">BetterDesk native desktop session over CDAP WebSocket.</span>
|
|
</div>
|
|
<div class="cdap-desktop-canvas-wrap">
|
|
<canvas class="cdap-desktop-canvas" width="1280" height="720"></canvas>
|
|
<div class="cdap-desktop-overlay">
|
|
<span class="material-icons">hourglass_top</span>
|
|
<span>Waiting for desktop session...</span>
|
|
</div>
|
|
</div>
|
|
<div class="cdap-desktop-connect">
|
|
<button class="btn cdap-desktop-connect-btn" data-widget="remote-full" type="button">
|
|
<span class="material-icons">play_arrow</span>
|
|
Connect desktop
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<!-- Hidden audio widget element so cdap-audio.js can mount its level
|
|
meter / status without rendering a visible UI; controlled from
|
|
the toolbar Audio button. -->
|
|
<div class="cdap-audio-widget" id="wval-remote-audio" style="display:none" aria-hidden="true">
|
|
<div class="cdap-audio-status"></div>
|
|
<div class="cdap-audio-level"><div class="cdap-audio-level-fill"></div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script nonce="${cspNonce}">
|
|
(function () {
|
|
const deviceId = ${JSON.stringify(deviceId)};
|
|
const widgetId = 'remote-full';
|
|
|
|
function openDesktopSession() {
|
|
const connectWrap = document.querySelector('#wval-remote-full .cdap-desktop-connect');
|
|
connectWrap?.classList.add('hidden');
|
|
if (window.CDAPDesktop) {
|
|
window.CDAPDesktop.close(deviceId, widgetId);
|
|
window.CDAPDesktop.open(deviceId, widgetId);
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
document.querySelector('.cdap-desktop-connect-btn')?.addEventListener('click', openDesktopSession);
|
|
document.getElementById('bd-remote-reconnect')?.addEventListener('click', openDesktopSession);
|
|
|
|
// Fullscreen + Keyboard Lock (Phase 3.3) ----------------------------
|
|
const fsBtn = document.getElementById('bd-remote-fullscreen');
|
|
const fsIcon = fsBtn?.querySelector('.material-icons');
|
|
fsBtn?.addEventListener('click', async () => {
|
|
const fs = await window.CDAPDesktop?.toggleFullscreen(deviceId, widgetId);
|
|
if (fsIcon) fsIcon.textContent = fs ? 'fullscreen_exit' : 'fullscreen';
|
|
});
|
|
document.addEventListener('fullscreenchange', () => {
|
|
if (!fsIcon) return;
|
|
fsIcon.textContent = window.CDAPDesktop?.isFullscreen(deviceId, widgetId) ? 'fullscreen_exit' : 'fullscreen';
|
|
});
|
|
|
|
// Session recording (Phase 3.4) -------------------------------------
|
|
const recBtn = document.getElementById('bd-remote-record');
|
|
const recIcon = recBtn?.querySelector('.material-icons');
|
|
recBtn?.addEventListener('click', async () => {
|
|
if (window.CDAPDesktop?.isRecording(deviceId, widgetId)) {
|
|
await window.CDAPDesktop.downloadRecording(deviceId, widgetId);
|
|
if (recIcon) recIcon.textContent = 'fiber_manual_record';
|
|
recBtn.classList.remove('recording');
|
|
} else {
|
|
const ok = window.CDAPDesktop?.startRecording(deviceId, widgetId);
|
|
if (ok) {
|
|
if (recIcon) recIcon.textContent = 'stop_circle';
|
|
recBtn.classList.add('recording');
|
|
} else {
|
|
alert('Recording is not supported in this browser, or the session is not connected yet.');
|
|
}
|
|
}
|
|
});
|
|
|
|
// Remote audio toggle (Phase 3.8) -----------------------------------
|
|
const audioBtn = document.getElementById('bd-remote-audio');
|
|
const audioIcon = audioBtn?.querySelector('.material-icons');
|
|
const audioWidgetId = 'remote-audio';
|
|
let audioActive = false;
|
|
audioBtn?.addEventListener('click', () => {
|
|
if (!window.CDAPAudio) {
|
|
console.warn('[remote-cdap] CDAPAudio module unavailable');
|
|
return;
|
|
}
|
|
if (audioActive) {
|
|
window.CDAPAudio.close(deviceId, audioWidgetId);
|
|
audioActive = false;
|
|
if (audioIcon) audioIcon.textContent = 'volume_up';
|
|
audioBtn.classList.remove('active');
|
|
} else {
|
|
window.CDAPAudio.open(deviceId, audioWidgetId, { direction: 'receive' });
|
|
audioActive = true;
|
|
if (audioIcon) audioIcon.textContent = 'volume_off';
|
|
audioBtn.classList.add('active');
|
|
}
|
|
});
|
|
|
|
// Virtual paste (Phase 3.2) ---------------------------------------
|
|
// Reads the operator clipboard via navigator.clipboard.readText()
|
|
// and types it into the remote session as a single text input
|
|
// event. Useful when the device side refuses incoming clipboard
|
|
// sync, or when the operator wants to paste from outside the page.
|
|
document.getElementById('bd-remote-paste')?.addEventListener('click', async () => {
|
|
try {
|
|
const ok = await window.CDAPDesktop?.pasteFromClipboard(deviceId, widgetId);
|
|
if (!ok) {
|
|
alert('Could not read the clipboard. Grant clipboard permission to this site or focus the canvas first.');
|
|
}
|
|
} catch (err) {
|
|
console.warn('[remote-cdap] paste failed:', err && err.message);
|
|
}
|
|
});
|
|
|
|
openDesktopSession();
|
|
});
|
|
})();
|
|
</script>
|
|
`
|
|
}) %>
|