Files
BetterDesk/web-nodejs/views/remote.ejs
T
UNITRONIX 01fcdc4aea feat(billing): implement billing and time synchronization module
Added a new billing service and time synchronization functionality to the server. This includes the creation of billing packages, contracts, and sessions, along with necessary database migrations. Updated API endpoints for billing management and integrated billing checks into signal handling. Enhanced configuration options for billing parameters and added localization support for billing-related messages.
2026-06-07 14:35:22 +02:00

344 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">
<!-- 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>
<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>
`
}) %>