mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-11 01:55:43 +00:00
2e01955f6c
- Introduced server URL validation via `GET /api/bd/server-info` and `probe_server_url`. - Added LAN discovery capabilities using UDP and optional mDNS for local network panel detection. - Implemented a local settings window for managing URL, TLS settings, and user preferences. - Enhanced dashboard with a unified sidebar and improved scrolling behavior. - Updated documentation and pre-release checklist to reflect new features and requirements.
349 lines
18 KiB
HTML
349 lines
18 KiB
HTML
<%- include('layouts/viewer', {
|
|
title: title || _('remote.title'),
|
|
pageScripts: ['billing-report', 'remote'],
|
|
body: `
|
|
|
|
<!-- Session Tab Bar -->
|
|
<div class="session-tab-bar" id="session-tab-bar">
|
|
<button type="button" class="tab-bar-back" id="btn-back-devices" title="${_('remote.back_to_devices')}">
|
|
<span class="material-icons">arrow_back</span>
|
|
</button>
|
|
<div class="session-tabs" id="session-tabs"></div>
|
|
<button class="tab-bar-add" id="btn-add-session" title="${_('remote.new_connection') || 'New Connection'}">
|
|
<span class="material-icons">add</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Main Viewer Container (session panels are inserted here) -->
|
|
<div class="viewer-container" id="viewer-container"></div>
|
|
|
|
<!-- Shared floating toolbar (collapsible RustDesk-style pill, applies to active session) -->
|
|
<div class="viewer-toolbar" id="viewer-toolbar">
|
|
<!-- Expanded action pill (shown only when the toolbar is expanded) -->
|
|
<div class="toolbar-expanded" id="toolbar-expanded">
|
|
<div class="toolbar-left">
|
|
<span class="toolbar-device-info">
|
|
<span class="toolbar-device-id" id="toolbar-device-id"></span>
|
|
<span class="toolbar-separator">|</span>
|
|
<span class="toolbar-status" id="toolbar-status">${_('remote.connecting')}</span>
|
|
</span>
|
|
</div>
|
|
<div class="toolbar-center">
|
|
<span class="toolbar-stats" id="toolbar-stats"></span>
|
|
</div>
|
|
<div class="toolbar-right">
|
|
<select id="viewer-language-select" class="toolbar-lang-select" data-language-select aria-label="${_('settings.language')}"></select>
|
|
<!-- Actions Menu -->
|
|
<div class="toolbar-dropdown">
|
|
<button class="toolbar-btn" id="btn-actions" title="${_('remote.actions')}">
|
|
<span class="material-icons">flash_on</span>
|
|
</button>
|
|
<div class="toolbar-dropdown-menu" id="actions-menu">
|
|
<button class="dropdown-item" id="btn-cad">
|
|
<span class="material-icons">security</span> Ctrl+Alt+Del
|
|
</button>
|
|
<button class="dropdown-item" id="btn-lock">
|
|
<span class="material-icons">lock</span> ${_('remote.lock_screen')}
|
|
</button>
|
|
<button class="dropdown-item" id="btn-restart-remote">
|
|
<span class="material-icons">restart_alt</span> ${_('remote.restart_remote')}
|
|
</button>
|
|
<div class="dropdown-separator"></div>
|
|
<button class="dropdown-item" id="btn-refresh-screen">
|
|
<span class="material-icons">refresh</span> ${_('remote.refresh_screen')}
|
|
</button>
|
|
<button class="dropdown-item" id="btn-clipboard-paste">
|
|
<span class="material-icons">content_paste</span> ${_('remote.clipboard_paste')}
|
|
</button>
|
|
<div class="dropdown-separator"></div>
|
|
<button class="dropdown-item toggle-item" id="btn-block-input" data-active="false">
|
|
<span class="material-icons">block</span> ${_('remote.block_input')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Display Menu -->
|
|
<div class="toolbar-dropdown">
|
|
<button class="toolbar-btn" id="btn-display" title="${_('remote.display_settings')}">
|
|
<span class="material-icons">tune</span>
|
|
</button>
|
|
<div class="toolbar-dropdown-menu toolbar-dropdown-wide" id="display-menu">
|
|
<div class="dropdown-label">${_('remote.image_quality')}</div>
|
|
<button class="dropdown-item quality-item active" data-quality="Best">
|
|
<span class="material-icons">hd</span> ${_('remote.quality_best')}
|
|
</button>
|
|
<button class="dropdown-item quality-item" data-quality="Balanced">
|
|
<span class="material-icons">tune</span> ${_('remote.quality_balanced')}
|
|
</button>
|
|
<button class="dropdown-item quality-item" data-quality="Low">
|
|
<span class="material-icons">speed</span> ${_('remote.quality_low')}
|
|
</button>
|
|
<div class="dropdown-separator"></div>
|
|
<div class="dropdown-label">${_('remote.codec')}</div>
|
|
<button class="dropdown-item codec-item active" data-codec="Auto">
|
|
<span class="material-icons">auto_awesome</span> ${_('remote.codec_auto')}
|
|
</button>
|
|
<button class="dropdown-item codec-item" data-codec="VP9">
|
|
<span class="material-icons">memory</span> VP9
|
|
</button>
|
|
<button class="dropdown-item codec-item" data-codec="AV1">
|
|
<span class="material-icons">memory</span> AV1
|
|
</button>
|
|
<button class="dropdown-item codec-item" data-codec="H264">
|
|
<span class="material-icons">memory</span> H264
|
|
</button>
|
|
<button class="dropdown-item codec-item" data-codec="VP8">
|
|
<span class="material-icons">memory</span> VP8
|
|
</button>
|
|
<button class="dropdown-item codec-item" data-codec="H265">
|
|
<span class="material-icons">memory</span> H265
|
|
</button>
|
|
<div class="dropdown-separator"></div>
|
|
<div class="dropdown-label">${_('remote.scale_mode')}</div>
|
|
<button class="dropdown-item scale-item active" data-scale="fit">
|
|
<span class="material-icons">fit_screen</span> ${_('remote.scale_fit')}
|
|
</button>
|
|
<button class="dropdown-item scale-item" data-scale="fill">
|
|
<span class="material-icons">crop_free</span> ${_('remote.scale_fill')}
|
|
</button>
|
|
<button class="dropdown-item scale-item" data-scale="1:1">
|
|
<span class="material-icons">filter_1</span> ${_('remote.scale_original')}
|
|
</button>
|
|
<button class="dropdown-item scale-item" data-scale="stretch">
|
|
<span class="material-icons">open_in_full</span> ${_('remote.scale_stretch')}
|
|
</button>
|
|
<div class="dropdown-separator"></div>
|
|
<button class="dropdown-item toggle-item" id="btn-show-cursor" data-active="true">
|
|
<span class="material-icons">mouse</span> ${_('remote.show_cursor')}
|
|
</button>
|
|
<button class="dropdown-item toggle-item" id="btn-lock-session" data-active="false">
|
|
<span class="material-icons">lock_clock</span> ${_('remote.lock_after_session')}
|
|
</button>
|
|
<button class="dropdown-item toggle-item" id="btn-privacy-mode" data-active="false">
|
|
<span class="material-icons">visibility_off</span> ${_('remote.privacy_mode')}
|
|
</button>
|
|
<button class="dropdown-item toggle-item" id="btn-disable-clipboard" data-active="false">
|
|
<span class="material-icons">content_paste_off</span> ${_('remote.disable_clipboard')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Audio -->
|
|
<button class="toolbar-btn" id="btn-audio" title="${_('remote.toggle_audio')}">
|
|
<span class="material-icons">volume_up</span>
|
|
</button>
|
|
|
|
<!-- Monitor Selector -->
|
|
<div class="toolbar-dropdown">
|
|
<button class="toolbar-btn" id="btn-monitors" title="${_('remote.monitors') || 'Monitors'}" style="display:none">
|
|
<span class="material-icons">desktop_windows</span>
|
|
</button>
|
|
<div class="toolbar-dropdown-menu" id="monitors-menu">
|
|
<div class="dropdown-label">${_('remote.select_monitor') || 'Select Monitor'}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Chat -->
|
|
<button class="toolbar-btn" id="btn-chat" title="${_('remote.chat')}">
|
|
<span class="material-icons">chat</span>
|
|
</button>
|
|
|
|
<!-- File Transfer -->
|
|
<button class="toolbar-btn" id="btn-file-transfer" title="${_('remote.file_transfer') || 'File Transfer'}">
|
|
<span class="material-icons">folder_open</span>
|
|
</button>
|
|
|
|
<!-- Recording -->
|
|
<button class="toolbar-btn" id="btn-record" title="${_('remote.record')}">
|
|
<span class="material-icons">fiber_manual_record</span>
|
|
</button>
|
|
|
|
<!-- View Only -->
|
|
<button class="toolbar-btn" id="btn-viewonly" title="${_('remote.view_only')}">
|
|
<span class="material-icons">visibility</span>
|
|
</button>
|
|
|
|
<!-- Pin Toolbar -->
|
|
<button class="toolbar-btn" id="btn-pin" title="${_('remote.pin_toolbar')}">
|
|
<span class="material-icons">push_pin</span>
|
|
</button>
|
|
|
|
<!-- Fullscreen -->
|
|
<button class="toolbar-btn" id="btn-fullscreen" title="${_('remote.fullscreen')}">
|
|
<span class="material-icons">fullscreen</span>
|
|
</button>
|
|
|
|
<!-- Disconnect -->
|
|
<button class="toolbar-btn toolbar-btn-danger" id="btn-disconnect" title="${_('remote.disconnect')}">
|
|
<span class="material-icons">power_settings_new</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<!-- Always-visible compact handle: [move] [fullscreen] [expand/collapse] -->
|
|
<div class="toolbar-handle" id="toolbar-handle">
|
|
<button type="button" class="handle-btn handle-drag" id="btn-toolbar-drag" title="${_('remote.move_toolbar')}">
|
|
<span class="material-icons">drag_indicator</span>
|
|
</button>
|
|
<button type="button" class="handle-btn" id="btn-handle-fullscreen" title="${_('remote.fullscreen')}">
|
|
<span class="material-icons">fullscreen</span>
|
|
</button>
|
|
<button type="button" class="handle-btn handle-expand" id="btn-toolbar-expand" title="${_('remote.toggle_menu')}">
|
|
<span class="material-icons">expand_more</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Session Dialog -->
|
|
<div class="viewer-overlay" id="add-session-overlay" style="display:none;">
|
|
<div class="overlay-card">
|
|
<div class="overlay-icon"><span class="material-icons">add_circle</span></div>
|
|
<h2 class="overlay-title">${_('remote.new_connection') || 'New Connection'}</h2>
|
|
<div class="password-form">
|
|
<div class="form-group">
|
|
<input type="text" class="form-input" id="new-session-id"
|
|
placeholder="${_('remote.device_id_placeholder') || 'Enter Device ID'}" autocomplete="off">
|
|
</div>
|
|
<button class="btn btn-primary btn-full" id="btn-connect-new">
|
|
<span class="material-icons">login</span>
|
|
${_('remote.connect')}
|
|
</button>
|
|
<button class="btn btn-secondary btn-full" id="btn-cancel-new" style="margin-top:8px;">
|
|
${_('actions.cancel')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Session Panel Template (cloned for each session) -->
|
|
<template id="session-panel-template">
|
|
<div class="session-panel">
|
|
<div class="viewer-overlay session-connection-overlay">
|
|
<div class="overlay-card">
|
|
<div class="overlay-icon"><span class="material-icons">connected_tv</span></div>
|
|
<h2 class="overlay-title">${_('remote.title')}</h2>
|
|
<p class="overlay-device-id session-device-label"></p>
|
|
<p class="overlay-device-name session-device-name"></p>
|
|
<div class="overlay-status">
|
|
<div class="spinner"></div>
|
|
<span class="session-status-text">${_('remote.connecting')}</span>
|
|
</div>
|
|
<div class="overlay-actions session-overlay-actions" style="display:none;">
|
|
<button class="btn btn-primary session-btn-reconnect">
|
|
<span class="material-icons">refresh</span>
|
|
${_('remote.reconnect')}
|
|
</button>
|
|
<button class="btn btn-secondary session-btn-cdap-fallback" style="display:none;margin-top:8px;" title="${_('remote.cdap_fallback_hint')}">
|
|
<span class="material-icons">photo_camera</span>
|
|
${_('remote.use_cdap_fallback')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="viewer-overlay session-password-overlay" style="display:none;">
|
|
<div class="overlay-card">
|
|
<div class="overlay-icon"><span class="material-icons">lock</span></div>
|
|
<h2 class="overlay-title">${_('remote.password_required')}</h2>
|
|
<p class="overlay-device-id session-password-label"></p>
|
|
<div class="password-form">
|
|
<div class="form-group">
|
|
<input type="password" class="form-input session-password-input"
|
|
placeholder="${_('remote.password_placeholder')}" autocomplete="off">
|
|
</div>
|
|
<p class="login-error session-login-error" style="display:none;"></p>
|
|
<label class="form-check session-remember-peer" style="display:flex;align-items:center;gap:8px;margin:10px 0 0;font-size:0.875rem;">
|
|
<input type="checkbox" class="session-remember-peer-checkbox">
|
|
<span>${_('remote.remember_peer_password') || 'Remember device password on this device'}</span>
|
|
</label>
|
|
<button class="btn btn-primary btn-full session-btn-authenticate">
|
|
<span class="material-icons">login</span>
|
|
${_('remote.connect')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="viewer-overlay session-2fa-overlay" style="display:none;">
|
|
<div class="overlay-card">
|
|
<div class="overlay-icon"><span class="material-icons">verified_user</span></div>
|
|
<h2 class="overlay-title">${_('remote.2fa_required') || '2FA Verification'}</h2>
|
|
<p class="overlay-subtitle">${_('remote.2fa_hint') || 'Enter the 6-digit code from your authenticator app'}</p>
|
|
<div class="password-form">
|
|
<div class="form-group">
|
|
<input type="text" class="form-input session-2fa-input"
|
|
placeholder="000000" autocomplete="one-time-code"
|
|
maxlength="6" inputmode="numeric" pattern="[0-9]*"
|
|
style="text-align:center;font-size:1.5rem;letter-spacing:0.5em;">
|
|
</div>
|
|
<p class="login-error session-2fa-error" style="display:none;"></p>
|
|
<button class="btn btn-primary btn-full session-btn-verify-2fa">
|
|
<span class="material-icons">verified</span>
|
|
${_('remote.verify') || 'Verify'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<canvas class="session-canvas" tabindex="0"></canvas>
|
|
<div class="chat-panel session-chat-panel" style="display:none;">
|
|
<div class="chat-header">
|
|
<span>${_('remote.chat')}</span>
|
|
<button class="chat-close session-btn-chat-close"><span class="material-icons">close</span></button>
|
|
</div>
|
|
<div class="chat-messages session-chat-messages"></div>
|
|
<div class="chat-input-area">
|
|
<input type="text" class="chat-input session-chat-input" placeholder="${_('remote.chat_placeholder')}" />
|
|
<button class="chat-send session-btn-chat-send"><span class="material-icons">send</span></button>
|
|
</div>
|
|
</div>
|
|
<!-- File Transfer Panel -->
|
|
<div class="file-panel session-file-panel" style="display:none;">
|
|
<div class="file-panel-header">
|
|
<span class="material-icons">folder_open</span>
|
|
<span>${_('remote.file_transfer') || 'File Transfer'}</span>
|
|
<div class="file-panel-actions">
|
|
<button class="file-btn-icon session-file-btn-hidden" title="${_('remote.file_show_hidden') || 'Show Hidden'}">
|
|
<span class="material-icons">visibility</span>
|
|
</button>
|
|
<button class="file-btn-icon session-file-btn-newdir" title="${_('remote.file_new_folder') || 'New Folder'}">
|
|
<span class="material-icons">create_new_folder</span>
|
|
</button>
|
|
<button class="file-btn-icon session-file-btn-upload" title="${_('remote.file_upload') || 'Upload'}">
|
|
<span class="material-icons">upload_file</span>
|
|
</button>
|
|
<button class="file-btn-icon session-file-btn-close">
|
|
<span class="material-icons">close</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="file-panel-path">
|
|
<button class="file-btn-icon session-file-btn-up" title="${_('remote.file_parent') || 'Parent Directory'}">
|
|
<span class="material-icons">arrow_upward</span>
|
|
</button>
|
|
<button class="file-btn-icon session-file-btn-home" title="${_('remote.file_root') || 'Root'}">
|
|
<span class="material-icons">home</span>
|
|
</button>
|
|
<span class="file-path-text session-file-path-text">/</span>
|
|
</div>
|
|
<div class="file-panel-list session-file-list">
|
|
<div class="file-empty">${_('remote.file_empty') || 'No files'}</div>
|
|
</div>
|
|
<div class="file-panel-transfers session-file-transfers" style="display:none;">
|
|
<div class="file-transfers-header">
|
|
<span class="material-icons">swap_vert</span>
|
|
<span>${_('remote.file_transfers') || 'Transfers'}</span>
|
|
</div>
|
|
<div class="file-transfers-list session-file-transfers-list"></div>
|
|
</div>
|
|
<input type="file" class="session-file-upload-input" multiple style="display:none;" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Initial session data (from server route) -->
|
|
<script nonce="${cspNonce}">
|
|
window.__initialDeviceId = ` + JSON.stringify(deviceId) + `;
|
|
window.__initialDeviceName = ` + JSON.stringify(device && device.hostname ? device.hostname : '') + `;
|
|
window.__capabilities = ` + JSON.stringify(capabilities || { transport: 'rd' }) + `;
|
|
</script>
|
|
|
|
`
|
|
}) %>
|