mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-10-26 14:30:19 +00:00
d57fe3dc49
Adds local verification and distribution of signed generic incoming-only agent releases, mobile-friendly QR activation, and the supporting generator interfaces. Includes the current staged console, documentation, localization, and builder changes.\n\nThanks: INSOLVE (Honorary); Marco Jakobs (@jacotec); MyNameisStitch (@MyNameisStitch); Redspin (@playerumpknow)
405 lines
27 KiB
HTML
405 lines
27 KiB
HTML
<%- include('layouts/main', {
|
|
title: _('nav.dashboard'),
|
|
pageStyles: ['dashboard'],
|
|
pageScripts: ['dashboard'],
|
|
currentPage: 'dashboard',
|
|
body: `
|
|
<div class="dashboard-shell" id="welcome-section">
|
|
<section class="dashboard-hero" aria-labelledby="welcome-greeting-text">
|
|
<div class="dashboard-hero-copy">
|
|
<span class="dashboard-eyebrow">${_('dashboard.title')}</span>
|
|
<h1 id="welcome-greeting-text"><span class="skeleton skeleton-text" style="width:260px;display:inline-block"></span></h1>
|
|
<p class="welcome-subtitle">${_('dashboard.subtitle')}</p>
|
|
</div>
|
|
<div class="dashboard-hero-actions">
|
|
<div class="system-pill" id="dashboard-system-pill" aria-live="polite">
|
|
<span class="status-dot"></span>
|
|
<span id="dashboard-system-status">${_('status.checking')}</span>
|
|
</div>
|
|
<button class="btn btn-secondary btn-sm" id="refresh-status-btn">
|
|
<span class="material-icons" aria-hidden="true">refresh</span>
|
|
${_('actions.refresh')}
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="dashboard-tip" id="welcome-tip">
|
|
<span class="material-icons tip-icon" aria-hidden="true">lightbulb</span>
|
|
<span class="tip-text" id="tip-text"></span>
|
|
<button class="tip-dismiss" id="tip-dismiss" title="${_('actions.dismiss')}" aria-label="${_('actions.dismiss')}">
|
|
<span class="material-icons" aria-hidden="true">close</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="dashboard-layout">
|
|
<main class="dashboard-primary" aria-label="${_('dashboard.title')}">
|
|
<section class="dashboard-stats" aria-label="${_('dashboard.health_overview')}">
|
|
<article class="dashboard-stat-card">
|
|
<div class="dashboard-stat-icon devices">
|
|
<span class="material-icons" aria-hidden="true">devices</span>
|
|
</div>
|
|
<div class="dashboard-stat-content">
|
|
<div class="dashboard-stat-value" id="stat-total">
|
|
<span class="skeleton skeleton-text"></span>
|
|
</div>
|
|
<div class="dashboard-stat-label">${_('dashboard.total_devices')}</div>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="dashboard-stat-card">
|
|
<div class="dashboard-stat-icon online">
|
|
<span class="material-icons" aria-hidden="true">wifi</span>
|
|
</div>
|
|
<div class="dashboard-stat-content">
|
|
<div class="dashboard-stat-value" id="stat-online">
|
|
<span class="skeleton skeleton-text"></span>
|
|
</div>
|
|
<div class="dashboard-stat-label">${_('dashboard.online_now')}</div>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="dashboard-stat-card">
|
|
<div class="dashboard-stat-icon banned">
|
|
<span class="material-icons" aria-hidden="true">block</span>
|
|
</div>
|
|
<div class="dashboard-stat-content">
|
|
<div class="dashboard-stat-value" id="stat-banned">
|
|
<span class="skeleton skeleton-text"></span>
|
|
</div>
|
|
<div class="dashboard-stat-label">${_('dashboard.banned')}</div>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="dashboard-stat-card">
|
|
<div class="dashboard-stat-icon offline">
|
|
<span class="material-icons" aria-hidden="true">cloud_off</span>
|
|
</div>
|
|
<div class="dashboard-stat-content">
|
|
<div class="dashboard-stat-value" id="stat-connections">
|
|
<span class="skeleton skeleton-text"></span>
|
|
</div>
|
|
<div class="dashboard-stat-label">${_('dashboard.offline_devices')}</div>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="dashboard-stat-card">
|
|
<div class="dashboard-stat-icon uptime">
|
|
<span class="material-icons" aria-hidden="true">schedule</span>
|
|
</div>
|
|
<div class="dashboard-stat-content">
|
|
<div class="dashboard-stat-value" id="stat-uptime">
|
|
<span class="skeleton skeleton-text"></span>
|
|
</div>
|
|
<div class="dashboard-stat-label">${_('dashboard.server_uptime')}</div>
|
|
</div>
|
|
</article>
|
|
</section>
|
|
|
|
<section class="client-config-card" aria-labelledby="client-config-title">
|
|
<div class="client-config-header">
|
|
<div>
|
|
<span class="dashboard-eyebrow">${_('dashboard.client_config_eyebrow')}</span>
|
|
<h2 class="section-title" id="client-config-title">
|
|
<span class="material-icons" aria-hidden="true">integration_instructions</span>
|
|
${_('dashboard.client_config_title')}
|
|
</h2>
|
|
<p class="client-config-subtitle">${_('dashboard.client_config_subtitle')}</p>
|
|
</div>
|
|
<div class="client-config-actions">
|
|
<button type="button" class="btn btn-secondary btn-sm" id="copy-client-config-btn">
|
|
<span class="material-icons" aria-hidden="true">content_copy</span>
|
|
${_('dashboard.copy_all_config')}
|
|
</button>
|
|
<button type="button" class="btn btn-secondary btn-sm" id="copy-deploy-string-btn">
|
|
<span class="material-icons" aria-hidden="true">terminal</span>
|
|
${_('dashboard.copy_deploy_string')}
|
|
</button>
|
|
<button type="button" class="btn btn-secondary btn-sm" id="show-intune-script-btn">
|
|
<span class="material-icons" aria-hidden="true">code</span>
|
|
${_('dashboard.show_intune_script')}
|
|
</button>
|
|
<button type="button" class="btn btn-secondary btn-sm" id="show-client-qr-btn">
|
|
<span class="material-icons" aria-hidden="true">qr_code_2</span>
|
|
${_('dashboard.show_config_qr')}
|
|
</button>
|
|
${typeof hasPermission === 'function' && hasPermission('enrollment.approve') ? '<button type="button" class="btn btn-primary btn-sm" id="activate-agent-qr-btn">' : ''}
|
|
${typeof hasPermission === 'function' && hasPermission('enrollment.approve') ? '<span class="material-icons" aria-hidden="true">qr_code_scanner</span>' : ''}
|
|
${typeof hasPermission === 'function' && hasPermission('enrollment.approve') ? _('dashboard.activate_agent_qr') : ''}
|
|
${typeof hasPermission === 'function' && hasPermission('enrollment.approve') ? '</button>' : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="client-config-host-row" id="client-config-host-row">
|
|
<label class="client-config-host-label" for="client-config-host-input">${_('dashboard.client_server_host_label')}</label>
|
|
<div class="client-config-host-controls">
|
|
<input type="text" class="form-input client-config-host-input" id="client-config-host-input"
|
|
placeholder="${_('dashboard.client_server_host_placeholder')}" autocomplete="off">
|
|
<button type="button" class="btn btn-secondary btn-sm" id="apply-client-host-btn">${_('dashboard.apply_client_host')}</button>
|
|
<span class="client-config-env-badge" id="client-config-env-badge" hidden title="${_('dashboard.client_server_host_env_hint')}">${_('dashboard.client_host_env_badge') || 'From env'}</span>
|
|
</div>
|
|
<p class="client-config-host-hint" id="client-config-host-hint">${_('dashboard.client_server_host_hint')}</p>
|
|
<p class="client-config-host-env-link" id="client-config-host-env-link" hidden>
|
|
<a href="/settings#public-endpoints">${_('dashboard.client_host_settings_link') || 'Open Settings → Public client endpoints'}</a>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="client-config-grid">
|
|
<div class="client-config-field">
|
|
<span class="client-config-label">${_('dashboard.config_server_id')}</span>
|
|
<code class="client-config-value" id="client-config-server-id"><span class="skeleton skeleton-text"></span></code>
|
|
<button type="button" class="client-config-copy" data-copy-target="client-config-server-id" title="${_('actions.copy')}" aria-label="${_('actions.copy')} ${_('dashboard.config_server_id')}">
|
|
<span class="material-icons" aria-hidden="true">content_copy</span>
|
|
</button>
|
|
</div>
|
|
<div class="client-config-field">
|
|
<span class="client-config-label">${_('dashboard.config_relay_server')}</span>
|
|
<code class="client-config-value" id="client-config-relay-server"><span class="skeleton skeleton-text"></span></code>
|
|
<button type="button" class="client-config-copy" data-copy-target="client-config-relay-server" title="${_('actions.copy')}" aria-label="${_('actions.copy')} ${_('dashboard.config_relay_server')}">
|
|
<span class="material-icons" aria-hidden="true">content_copy</span>
|
|
</button>
|
|
</div>
|
|
<div class="client-config-field">
|
|
<span class="client-config-label">${_('dashboard.config_api_server')}</span>
|
|
<code class="client-config-value" id="client-config-api-url"><span class="skeleton skeleton-text"></span></code>
|
|
<button type="button" class="client-config-copy" data-copy-target="client-config-api-url" title="${_('actions.copy')}" aria-label="${_('actions.copy')} ${_('dashboard.config_api_server')}">
|
|
<span class="material-icons" aria-hidden="true">content_copy</span>
|
|
</button>
|
|
</div>
|
|
<div class="client-config-field client-config-field-wide">
|
|
<span class="client-config-label">${_('dashboard.config_key')}</span>
|
|
<code class="client-config-value is-masked" id="client-config-public-key"><span class="skeleton skeleton-text"></span></code>
|
|
<button type="button" class="client-config-reveal" id="client-config-key-reveal" title="${_('actions.show_value')}" aria-label="${_('actions.show_value')} ${_('dashboard.config_key')}" aria-pressed="false">
|
|
<span class="material-icons" aria-hidden="true">visibility</span>
|
|
</button>
|
|
<button type="button" class="client-config-copy" data-copy-target="client-config-public-key" title="${_('actions.copy')}" aria-label="${_('actions.copy')} ${_('dashboard.config_key')}">
|
|
<span class="material-icons" aria-hidden="true">content_copy</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="client-config-footer">
|
|
<div class="client-config-onboarding">
|
|
<p>${_('dashboard.client_config_hint')}</p>
|
|
<ol class="client-config-steps">
|
|
<li><a href="/generator">${_('dashboard.open_generator')}</a> — ${_('dashboard.generate_config')}</li>
|
|
<li>${_('dashboard.show_config_qr')} / ${_('dashboard.copy_all_config')} — ${_('dashboard.config_api_server')}</li>
|
|
<li>${_('registrations.title')} — ${_('registrations.subtitle')}</li>
|
|
</ol>
|
|
</div>
|
|
<div class="client-config-links">
|
|
<a class="btn btn-primary btn-sm" href="/generator">
|
|
<span class="material-icons" aria-hidden="true">build</span>
|
|
${_('dashboard.open_generator')}
|
|
</a>
|
|
<a class="btn btn-secondary btn-sm" href="/keys">
|
|
<span class="material-icons" aria-hidden="true">vpn_key</span>
|
|
${_('dashboard.open_keys')}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="server-status-section">
|
|
<div class="section-header">
|
|
<h2 class="section-title">
|
|
<span class="material-icons" aria-hidden="true">dns</span>
|
|
${_('dashboard.server_status')}
|
|
</h2>
|
|
</div>
|
|
|
|
<div class="server-status-grid">
|
|
<article class="server-status-card">
|
|
<div class="server-status-header">
|
|
<span class="server-status-title">
|
|
<span class="material-icons" aria-hidden="true">router</span>
|
|
Signal Server
|
|
</span>
|
|
<span class="server-status-indicator" id="hbbs-status">
|
|
<span class="status-dot"></span>
|
|
<span class="status-text">${_('status.checking')}</span>
|
|
</span>
|
|
</div>
|
|
<div class="server-status-details">
|
|
<div class="server-detail-row">
|
|
<span class="server-detail-label">Signal (TCP/UDP)</span>
|
|
<span class="server-detail-value" id="hbbs-port">-</span>
|
|
</div>
|
|
<div class="server-detail-row">
|
|
<span class="server-detail-label">NAT Test (TCP)</span>
|
|
<span class="server-detail-value" id="nat-port">-</span>
|
|
</div>
|
|
<div class="server-detail-row">
|
|
<span class="server-detail-label">WS Signal</span>
|
|
<span class="server-detail-value" id="ws-signal-port">-</span>
|
|
</div>
|
|
<div class="server-detail-row">
|
|
<span class="server-detail-label">REST API (HTTP)</span>
|
|
<span class="server-detail-value" id="api-port">-</span>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="server-status-card">
|
|
<div class="server-status-header">
|
|
<span class="server-status-title">
|
|
<span class="material-icons" aria-hidden="true">swap_horiz</span>
|
|
Relay Server
|
|
</span>
|
|
<span class="server-status-indicator" id="hbbr-status">
|
|
<span class="status-dot"></span>
|
|
<span class="status-text">${_('status.checking')}</span>
|
|
</span>
|
|
</div>
|
|
<div class="server-status-details">
|
|
<div class="server-detail-row">
|
|
<span class="server-detail-label">Relay (TCP)</span>
|
|
<span class="server-detail-value" id="hbbr-port">-</span>
|
|
</div>
|
|
<div class="server-detail-row">
|
|
<span class="server-detail-label">WS Relay</span>
|
|
<span class="server-detail-value" id="ws-relay-port">-</span>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="server-status-card">
|
|
<div class="server-status-header">
|
|
<span class="server-status-title">
|
|
<span class="material-icons" aria-hidden="true">web</span>
|
|
Web Console
|
|
</span>
|
|
<span class="server-status-indicator running">
|
|
<span class="status-dot"></span>
|
|
<span class="status-text">${_('status.running')}</span>
|
|
</span>
|
|
</div>
|
|
<div class="server-status-details">
|
|
<div class="server-detail-row">
|
|
<span class="server-detail-label">Console (HTTP)</span>
|
|
<span class="server-detail-value" id="console-port">-</span>
|
|
</div>
|
|
<div class="server-detail-row">
|
|
<span class="server-detail-label">Client API (HTTP)</span>
|
|
<span class="server-detail-value" id="client-api-port">-</span>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="quick-actions-section">
|
|
<div class="section-header">
|
|
<h2 class="section-title">
|
|
<span class="material-icons" aria-hidden="true">flash_on</span>
|
|
${_('dashboard.quick_actions')}
|
|
</h2>
|
|
</div>
|
|
|
|
<div class="quick-actions">
|
|
<a href="/devices" class="quick-action-card">
|
|
<div class="quick-action-icon">
|
|
<span class="material-icons" aria-hidden="true">devices</span>
|
|
</div>
|
|
<div class="quick-action-title">${_('nav.devices')}</div>
|
|
<div class="quick-action-desc">${_('dashboard.manage_devices')}</div>
|
|
</a>
|
|
|
|
<a href="/generator" class="quick-action-card">
|
|
<div class="quick-action-icon">
|
|
<span class="material-icons" aria-hidden="true">build</span>
|
|
</div>
|
|
<div class="quick-action-title">${_('nav.generator')}</div>
|
|
<div class="quick-action-desc">${_('dashboard.generate_config')}</div>
|
|
</a>
|
|
|
|
<a href="/remote" class="quick-action-card">
|
|
<div class="quick-action-icon">
|
|
<span class="material-icons" aria-hidden="true">desktop_windows</span>
|
|
</div>
|
|
<div class="quick-action-title">${_('nav.remote')}</div>
|
|
<div class="quick-action-desc">${_('dashboard.open_remote')}</div>
|
|
</a>
|
|
|
|
<a href="/settings" class="quick-action-card">
|
|
<div class="quick-action-icon">
|
|
<span class="material-icons" aria-hidden="true">settings</span>
|
|
</div>
|
|
<div class="quick-action-title">${_('nav.settings')}</div>
|
|
<div class="quick-action-desc">${_('dashboard.server_settings')}</div>
|
|
</a>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<aside class="dashboard-side-panel" aria-label="${_('dashboard.activity_feed')}">
|
|
<section class="activity-feed-section">
|
|
<div class="section-header">
|
|
<h2 class="section-title">
|
|
<span class="material-icons" aria-hidden="true">history</span>
|
|
${_('dashboard.activity_feed')}
|
|
</h2>
|
|
<a href="/settings#audit" class="btn btn-secondary btn-sm">
|
|
<span class="material-icons" aria-hidden="true">open_in_new</span>
|
|
${_('dashboard.view_audit_log') || _('actions.view_all')}
|
|
</a>
|
|
</div>
|
|
<div class="activity-list" id="activity-feed">
|
|
<div class="activity-empty">
|
|
<span class="skeleton skeleton-text" style="width:200px;display:inline-block"></span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</aside>
|
|
</div>
|
|
|
|
<div class="modal-overlay dashboard-agent-activation-modal" id="dashboard-agent-activation-modal"
|
|
role="dialog" aria-modal="true" aria-labelledby="dashboard-agent-activation-title" hidden>
|
|
<div class="modal dashboard-agent-activation-dialog">
|
|
<div class="modal-header">
|
|
<h2 id="dashboard-agent-activation-title">${_('dashboard.activate_agent_qr')}</h2>
|
|
<button type="button" class="btn-icon" id="dashboard-agent-activation-close"
|
|
aria-label="${_('actions.close')}">
|
|
<span class="material-icons" aria-hidden="true">close</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body dashboard-agent-activation-body">
|
|
<p class="form-hint">${_('dashboard.activate_agent_qr_hint')}</p>
|
|
<div class="dashboard-agent-activation-reader" id="dashboard-agent-activation-reader">
|
|
<video id="dashboard-agent-activation-video" autoplay muted playsinline></video>
|
|
<div class="dashboard-agent-activation-frame" aria-hidden="true"></div>
|
|
</div>
|
|
<input type="file" id="dashboard-agent-activation-file" accept="image/*"
|
|
class="dashboard-agent-activation-file">
|
|
<div class="dashboard-agent-activation-actions">
|
|
<button type="button" class="btn btn-secondary btn-sm" id="dashboard-agent-activation-file-btn">
|
|
<span class="material-icons" aria-hidden="true">image</span>
|
|
${_('dashboard.activate_agent_choose_image')}
|
|
</button>
|
|
<button type="button" class="btn btn-secondary btn-sm" id="dashboard-agent-activation-stop">
|
|
${_('actions.cancel')}
|
|
</button>
|
|
</div>
|
|
<p class="dashboard-agent-activation-status" id="dashboard-agent-activation-status"
|
|
aria-live="polite"></p>
|
|
<div class="dashboard-agent-activation-form" id="dashboard-agent-activation-form" hidden>
|
|
<label class="form-label" for="dashboard-agent-activation-name">
|
|
${_('dashboard.activate_agent_name')}
|
|
</label>
|
|
<input class="form-input" id="dashboard-agent-activation-name" maxlength="120">
|
|
<label class="form-label" for="dashboard-agent-activation-folder">
|
|
${_('dashboard.activate_agent_folder')}
|
|
</label>
|
|
<select class="form-input" id="dashboard-agent-activation-folder">
|
|
<option value="0">${_('dashboard.activate_agent_folder_none')}</option>
|
|
</select>
|
|
<button type="button" class="btn btn-primary" id="dashboard-agent-activation-submit">
|
|
${_('dashboard.activate_agent_submit')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
}) %>
|