Files
BetterDesk/web-nodejs/views/settings.ejs
T
UNITRONIX 8579e88be4 Fix split-domain RustDesk client config for dashboard and QR (Fixes #222).
Add PUBLIC_SERVER_ID, PUBLIC_RELAY_SERVER, and PUBLIC_API_URL with Settings UI,
unified endpoint resolution across Dashboard/Keys, and fallbacks via PANEL_PUBLIC_HOST.
2026-06-27 20:45:00 +02:00

2269 lines
144 KiB
HTML

<%- include('layouts/main', {
title: _('nav.settings'),
pageStyles: ['pages'],
pageScripts: ['branding-preview', 'settings'],
currentPage: 'settings',
breadcrumb: [{ label: _('nav.settings') }],
body: `
<div class="page-header">
<h1 class="page-title">${_('settings.title')}</h1>
<p class="page-subtitle">${_('settings.subtitle')}</p>
</div>
<div class="settings-shell">
<nav class="settings-shell-nav mobile-tabs-scroll" role="tablist" aria-label="${_('settings.title')}">
<button type="button" class="settings-shell-tab settings-tab active" role="tab" aria-selected="true" id="settings-tab-general" data-tab="general" aria-controls="tab-general">
<span class="material-icons" aria-hidden="true">tune</span>
${_('settings.tab_general')}
</button>
<button type="button" class="settings-shell-tab settings-tab" role="tab" aria-selected="false" id="settings-tab-branding" data-tab="branding" aria-controls="tab-branding">
<span class="material-icons" aria-hidden="true">palette</span>
${_('settings.tab_branding')}
</button>
${canServerConfig ? `<button type="button" class="settings-shell-tab settings-tab" role="tab" aria-selected="false" id="settings-tab-auth" data-tab="auth" aria-controls="tab-auth">
<span class="material-icons" aria-hidden="true">admin_panel_settings</span>
${_('settings.tab_auth')}
</button>
<button type="button" class="settings-shell-tab settings-tab" role="tab" aria-selected="false" id="settings-tab-backup" data-tab="backup" aria-controls="tab-backup">
<span class="material-icons" aria-hidden="true">backup</span>
${_('settings.tab_backup')}
</button>
<button type="button" class="settings-shell-tab settings-tab" role="tab" aria-selected="false" id="settings-tab-updates" data-tab="updates" aria-controls="tab-updates">
<span class="material-icons" aria-hidden="true">system_update</span>
${_('updates.tab_title')}
</button>
<button type="button" class="settings-shell-tab settings-tab" role="tab" aria-selected="false" id="settings-tab-email" data-tab="email" aria-controls="tab-email">
<span class="material-icons" aria-hidden="true">email</span>
${_('settings.tab_email')}
</button>
<button type="button" class="settings-shell-tab settings-tab" role="tab" aria-selected="false" id="settings-tab-advanced" data-tab="advanced" aria-controls="tab-advanced">
<span class="material-icons" aria-hidden="true">code</span>
${_('settings.tab_advanced')}
</button>` : ''}
</nav>
<div class="settings-shell-main">
<div class="settings-search" role="search">
<span class="material-icons" aria-hidden="true">search</span>
<input type="search" id="settings-search-input" class="form-input"
placeholder="${_('settings.ui.search_placeholder')}"
aria-controls="settings-search-status">
<button type="button" class="btn btn-ghost btn-sm" id="settings-search-clear" hidden>
<span class="material-icons">close</span>
${_('actions.clear')}
</button>
</div>
<div id="settings-search-status" class="settings-search-status" aria-live="polite" hidden></div>
<!-- General Tab -->
<div class="settings-tab-content active" id="tab-general" role="tabpanel" aria-labelledby="settings-tab-general">
<div class="settings-stack">
<!-- Password Change Section -->
<div class="settings-panel">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">lock</span>
${_('settings.change_password')}
</h2>
<p class="settings-panel-desc">${_('settings.password_desc')}</p>
</div>
<div class="settings-panel-body">
<form class="settings-form" id="password-form">
<div class="form-group">
<label class="form-label" for="current-password">${_('settings.current_password')}</label>
<input type="password" id="current-password" class="form-input" required>
</div>
<div class="form-group">
<label class="form-label" for="new-password">${_('settings.new_password')}</label>
<input type="password" id="new-password" class="form-input" required minlength="8">
</div>
<div class="form-group">
<label class="form-label" for="confirm-password">${_('settings.confirm_password')}</label>
<input type="password" id="confirm-password" class="form-input" required>
</div>
<div class="password-requirements">
<strong>${_('settings.password_requirements')}:</strong>
<ul>
<li id="req-length">${_('settings.req_length')}</li>
<li id="req-uppercase">${_('settings.req_uppercase')}</li>
<li id="req-lowercase">${_('settings.req_lowercase')}</li>
<li id="req-number">${_('settings.req_number')}</li>
</ul>
</div>
<button type="submit" class="btn btn-primary">
<span class="material-icons">save</span>
${_('settings.save_password')}
</button>
</form>
</div>
</div>
<!-- Server Information Section -->
<div class="settings-panel">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">info</span>
${_('settings.server_info')}
</h2>
</div>
<div class="settings-panel-body">
<div class="server-info-grid">
<div class="server-info-item">
<span class="server-info-label">
<span class="material-icons">tag</span>
${_('settings.version')}
</span>
<span class="server-info-value">v${appVersion}</span>
</div>
<div class="server-info-item">
<span class="server-info-label">
<span class="material-icons">dns</span>
${_('settings.go_server_version')}
</span>
<span class="server-info-value" id="go-server-version">-</span>
</div>
<div class="server-info-item">
<span class="server-info-label">
<span class="material-icons">storage</span>
${_('settings.database')}
</span>
<span class="server-info-value" id="db-path">-</span>
</div>
<div class="server-info-item">
<span class="server-info-label">
<span class="material-icons">schedule</span>
${_('settings.uptime')}
</span>
<span class="server-info-value" id="uptime">-</span>
</div>
</div>
</div>
</div>
${canServerConfig ? `
<div class="settings-panel" id="mesh-settings-panel">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">hub</span>
${_('mesh.settings_title')}
</h2>
<p class="settings-panel-desc">${_('mesh.settings_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="server-info-grid">
<div class="server-info-item">
<span class="server-info-label">${_('mesh.enabled')}</span>
<span class="server-info-value" id="mesh-status-enabled">-</span>
</div>
<div class="server-info-item">
<span class="server-info-label">${_('mesh.agents_online')}</span>
<span class="server-info-value" id="mesh-agents-online">-</span>
</div>
<div class="server-info-item">
<span class="server-info-label">${_('mesh.server_id')}</span>
<span class="server-info-value"><code id="mesh-server-id">-</code></span>
</div>
</div>
<div class="form-group" style="margin-top:1rem;">
<label class="form-label" for="mesh-msh-name">${_('mesh.mesh_name')}</label>
<input type="text" id="mesh-msh-name" class="form-input" value="BetterDesk Mesh">
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" id="mesh-copy-server-id">
<span class="material-icons">content_copy</span>
${_('mesh.copy_server_id')}
</button>
<a class="btn btn-primary" id="mesh-download-msh" href="/api/mesh/download.msh" download="meshagents.msh">
<span class="material-icons">download</span>
${_('mesh.download_msh')}
</a>
</div>
<p class="text-muted" style="margin-top:1rem;">${_('mesh.onboarding_body')}</p>
<div class="alert alert-warning mesh-cert-banner hidden" id="mesh-cert-banner"></div>
<ol class="mesh-onboarding-steps text-muted">
<li>${_('mesh.onboarding_step_msh')}</li>
<li>${_('mesh.onboarding_step_install')}</li>
<li>${_('mesh.onboarding_step_verify')}</li>
</ol>
<div class="settings-subsection" style="margin-top:1.5rem;">
<h3 class="settings-subsection-title">${_('mesh.groups_title')}</h3>
<div id="mesh-groups-list" class="mesh-groups-list"></div>
<div class="form-row" style="margin-top:8px;">
<input type="text" id="mesh-group-id" class="form-input" placeholder="${_('mesh.group_id_placeholder')}">
<input type="text" id="mesh-group-name" class="form-input" placeholder="${_('mesh.group_name_placeholder')}">
<button type="button" class="btn btn-secondary" id="mesh-group-add">${_('mesh.group_add')}</button>
</div>
<button type="button" class="btn btn-primary" id="mesh-groups-save" style="margin-top:8px;">${_('mesh.groups_save')}</button>
</div>
<div class="settings-subsection" style="margin-top:1.5rem;">
<h3 class="settings-subsection-title">${_('mesh.recordings_title')}</h3>
<p class="text-muted">${_('mesh.recordings_desc')}</p>
<div id="mesh-recordings-table" class="mesh-recordings-table"></div>
</div>
</div>
</div>
<!-- Connection Strategy (P2P / Relay) -->
<div class="settings-panel" id="connection-mode-section">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">swap_horiz</span>
${_('settings.connection_mode_title')}
</h2>
<p class="settings-panel-desc">${_('settings.connection_mode_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="alert alert-info" id="connection-mode-info">
<span class="material-icons">info</span>
<span>${_('settings.connection_mode_network_hint')}</span>
</div>
<div class="settings-choice-grid" role="radiogroup" aria-label="${_('settings.connection_mode_label')}">
<label class="settings-choice-card">
<input type="radio" name="connection-mode" value="p2p_first" id="conn-mode-p2p">
<span class="settings-choice-card-inner">
<span class="material-icons settings-choice-card-icon">swap_horiz</span>
<span class="settings-choice-card-title">${_('settings.connection_mode_p2p_first')}</span>
<span class="settings-choice-card-desc">${_('settings.ui.connection_p2p_hint')}</span>
</span>
</label>
<label class="settings-choice-card">
<input type="radio" name="connection-mode" value="relay_only" id="conn-mode-relay">
<span class="settings-choice-card-inner">
<span class="material-icons settings-choice-card-icon">hub</span>
<span class="settings-choice-card-title">${_('settings.connection_mode_relay_only')}</span>
<span class="settings-choice-card-desc">${_('settings.ui.connection_relay_hint')}</span>
</span>
</label>
</div>
<details class="connection-mode-advanced">
<summary>${_('settings.connection_mode_advanced')}</summary>
<div class="form-row" style="margin-top:1rem;">
<div class="form-group">
<label class="form-label" for="conn-p2p-fallback-ms">${_('settings.connection_mode_fallback_ms')}</label>
<input type="number" id="conn-p2p-fallback-ms" class="form-input" min="0" max="30000" step="100" value="2000">
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="conn-same-nat-relay" checked>
<span>${_('settings.connection_mode_same_nat_relay')}</span>
</label>
</div>
</div>
</details>
<p class="text-muted" id="connection-mode-runtime" hidden></p>
<p class="text-muted" id="connection-mode-source"></p>
<div class="form-actions">
<button type="button" class="btn btn-primary" id="connection-mode-save">
<span class="material-icons">save</span>
${_('settings.connection_mode_save')}
</button>
<button type="button" class="btn btn-warning" id="connection-mode-save-restart">
<span class="material-icons">restart_alt</span>
${_('settings.connection_mode_save_restart')}
</button>
</div>
</div>
</div>
<div class="settings-panel" id="public-endpoints-section">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">public</span>
${_('settings.public_endpoints_title')}
</h2>
<p class="settings-panel-desc">${_('settings.public_endpoints_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="alert alert-info">
<span class="material-icons">info</span>
<span>${_('settings.public_endpoints_hint')}</span>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="public-server-id">${_('settings.public_endpoints_server_id')}</label>
<input type="text" id="public-server-id" class="form-input"
placeholder="${_('settings.public_endpoints_server_id_placeholder')}" autocomplete="off">
</div>
<div class="form-group">
<label class="form-label" for="public-relay-server">${_('settings.public_endpoints_relay')}</label>
<input type="text" id="public-relay-server" class="form-input"
placeholder="${_('settings.public_endpoints_relay_placeholder')}" autocomplete="off">
</div>
</div>
<div class="form-group">
<label class="form-label" for="public-api-url">${_('settings.public_endpoints_api_url')}</label>
<input type="text" id="public-api-url" class="form-input"
placeholder="${_('settings.public_endpoints_api_url_placeholder')}" autocomplete="off">
<p class="text-muted form-hint">${_('settings.public_endpoints_api_url_hint')}</p>
</div>
<div class="form-actions">
<button type="button" class="btn btn-primary" id="public-endpoints-save">
<span class="material-icons">save</span>
${_('settings.public_endpoints_save')}
</button>
</div>
</div>
</div>
` : ''}
<!-- Two-Factor Authentication Section -->
<div class="settings-panel">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">security</span>
${_('settings.totp_title')}
</h2>
<p class="settings-panel-desc">${_('settings.totp_desc')}</p>
</div>
<div class="settings-panel-body">
<div id="totp-status-container">
<div class="totp-status-loading">
<span class="material-icons spin">sync</span>
${_('common.loading')}
</div>
</div>
</div>
</div>
<!-- Audit Log Section -->
<div class="settings-panel">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">history</span>
${_('settings.audit_log')}
</h2>
<p class="settings-panel-desc">${_('settings.audit_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="audit-log-container">
<table class="audit-log-table">
<thead>
<tr>
<th>${_('audit.time')}</th>
<th>${_('audit.user')}</th>
<th>${_('audit.action')}</th>
<th>${_('audit.details')}</th>
</tr>
</thead>
<tbody id="audit-log-body">
<tr>
<td colspan="4" class="text-center text-muted">
${_('common.loading')}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Tutorials Section -->
<div class="settings-panel">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">school</span>
${_('settings.tutorials_title')}
</h2>
<p class="settings-panel-desc">${_('settings.tutorials_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="settings-row">
<div class="settings-row-label">
<label class="form-label" for="tutorials-enabled">${_('settings.tutorials_enabled')}</label>
<p class="settings-row-hint">${_('settings.tutorials_enabled_hint')}</p>
</div>
<div class="settings-row-control">
<input type="checkbox" id="tutorials-enabled" class="toggle-switch" aria-describedby="tutorials-enabled-hint">
</div>
</div>
<div class="settings-row">
<div class="settings-row-label">
<span class="form-label">${_('tutorial.reset_all')}</span>
<p class="settings-row-hint" id="tutorials-reset-hint">${_('settings.tutorials_reset_hint')}</p>
</div>
<div class="settings-row-control">
<button type="button" class="btn btn-secondary btn-sm" id="tutorials-reset-btn">
<span class="material-icons">refresh</span>
${_('tutorial.reset_all')}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Branding Tab -->
<div class="settings-tab-content" id="tab-branding" role="tabpanel" aria-labelledby="settings-tab-branding" data-can-branding-edit="${canBrandingEdit ? '1' : '0'}">
<div class="branding-studio">
<section class="branding-panel-hero branding-hero">
<div class="branding-hero-copy">
<span class="branding-panel-hero-kicker branding-hero-kicker">${_('settings.tab_branding')}</span>
<h2>${_('branding.live_preview')}</h2>
<p>${_('branding.background_desc')}</p>
<div class="branding-panel-hero-steps branding-hero-steps">
<span><span class="material-icons" aria-hidden="true">palette</span>${_('branding.module_colors')}</span>
<span><span class="material-icons" aria-hidden="true">wallpaper</span>${_('branding.module_backgrounds')}</span>
<span><span class="material-icons" aria-hidden="true">connected_tv</span>${_('settings.ui.branding_rdclient')}</span>
</div>
</div>
</section>
<div class="branding-studio-toolbar">
<div class="branding-save-status" id="branding-save-status" aria-live="polite">
<span class="material-icons branding-status-icon">check_circle</span>
<span id="branding-status-text">${_('branding.status_saved')}</span>
</div>
<div class="branding-toolbar-actions">
<button type="button" class="btn btn-ghost btn-sm" id="branding-revert-btn" title="${_('branding.revert_changes')}">
<span class="material-icons">undo</span>
${_('branding.revert_changes')}
</button>
<button type="button" class="btn btn-primary btn-sm" id="branding-save-btn">
<span class="material-icons">save</span>
${_('branding.save_now')}
</button>
</div>
</div>
<div class="branding-readability-alert" id="branding-readability-alert" hidden>
<span class="material-icons">visibility</span>
<div>
<strong>Readability check</strong>
<p id="branding-readability-text"></p>
</div>
</div>
<div class="branding-profiles-bar">
<div class="branding-profiles-main">
<label class="form-label" for="branding-profile-select">${_('branding.profiles_label')}</label>
<select id="branding-profile-select" class="form-input form-select branding-profile-select"></select>
<button type="button" class="btn btn-secondary btn-sm" id="branding-profile-apply-btn">
<span class="material-icons">play_arrow</span>
${_('branding.profile_apply')}
</button>
</div>
<div class="branding-profiles-actions">
<button type="button" class="btn btn-ghost btn-sm" id="branding-profile-new-btn">
<span class="material-icons">add</span>
${_('branding.profile_new')}
</button>
<button type="button" class="btn btn-ghost btn-sm" id="branding-profile-save-btn">
<span class="material-icons">save_as</span>
${_('branding.profile_save')}
</button>
<button type="button" class="btn btn-ghost btn-sm" id="branding-profile-duplicate-btn">
<span class="material-icons">content_copy</span>
${_('branding.profile_duplicate')}
</button>
<button type="button" class="btn btn-ghost btn-sm btn-danger-text" id="branding-profile-delete-btn">
<span class="material-icons">delete</span>
${_('branding.profile_delete')}
</button>
</div>
</div>
<div class="branding-builtin-themes" id="branding-builtin-themes">
<h3 class="branding-builtin-title">${_('branding.builtin_themes')}</h3>
<div class="branding-theme-gallery" id="branding-theme-gallery"></div>
</div>
<div class="branding-studio-body">
<nav class="branding-module-nav" id="branding-module-nav" aria-label="${_('branding.modules_nav')}">
<button type="button" class="branding-module-btn active" data-branding-module="identity">
<span class="material-icons">badge</span>
${_('branding.module_identity')}
</button>
<button type="button" class="branding-module-btn" data-branding-module="colors">
<span class="material-icons">palette</span>
${_('branding.module_colors')}
</button>
<button type="button" class="branding-module-btn" data-branding-module="typography">
<span class="material-icons">text_format</span>
${_('branding.module_typography')}
</button>
<button type="button" class="branding-module-btn" data-branding-module="backgrounds">
<span class="material-icons">wallpaper</span>
${_('branding.module_backgrounds')}
</button>
<button type="button" class="branding-module-btn" data-branding-module="pages">
<span class="material-icons">web</span>
${_('branding.module_pages')}
</button>
<button type="button" class="branding-module-btn" data-branding-module="advanced">
<span class="material-icons">tune</span>
${_('branding.module_advanced')}
</button>
</nav>
<div class="settings-stack branding-modules" id="branding-modules">
<!-- Brand Identity -->
<div class="settings-panel branding-module-panel active" data-branding-module="identity">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">badge</span>
${_('branding.identity_title')}
</h2>
<p class="settings-panel-desc">${_('branding.identity_desc')}</p>
</div>
<div class="settings-panel-body">
<form class="settings-form branding-form" id="branding-identity-form" style="max-width: 600px;">
<div class="form-group">
<label class="form-label" for="brand-name">${_('branding.app_name')}</label>
<input type="text" id="brand-name" class="form-input" maxlength="50"
placeholder="BetterDesk">
</div>
<div class="form-group">
<label class="form-label" for="brand-description">${_('branding.app_description')}</label>
<input type="text" id="brand-description" class="form-input" maxlength="100"
placeholder="BetterDesk Server Management">
</div>
</form>
</div>
</div>
<!-- Logo Configuration -->
<div class="settings-panel branding-module-panel active" data-branding-module="identity">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">image</span>
${_('branding.logo_title')}
</h2>
<p class="settings-panel-desc">${_('branding.logo_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="branding-logo-config">
<div class="logo-type-selector">
<label class="logo-type-option">
<input type="radio" name="logo-type" value="text">
<span class="logo-type-card">
<span class="material-icons">title</span>
<span>${_('branding.logo_type_text')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="logo-type" value="icon">
<span class="logo-type-card">
<span class="material-icons">text_fields</span>
<span>${_('branding.logo_type_icon')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="logo-type" value="svg">
<span class="logo-type-card">
<span class="material-icons">code</span>
<span>${_('branding.logo_type_svg')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="logo-type" value="image">
<span class="logo-type-card">
<span class="material-icons">photo</span>
<span>${_('branding.logo_type_image')}</span>
</span>
</label>
</div>
<!-- Text Logo options -->
<div class="logo-config-panel hidden" id="logo-text-panel">
<div class="form-group">
<label class="form-label" for="logo-text-input">${_('branding.logo_text')}</label>
<input type="text" id="logo-text-input" class="form-input"
placeholder="insolve" maxlength="30">
<small class="form-hint">${_('branding.logo_text_hint')}</small>
</div>
<div class="form-group">
<label class="form-label" for="logo-text-accent">${_('branding.logo_text_accent')}</label>
<input type="text" id="logo-text-accent" class="form-input"
placeholder="Core Server" maxlength="30">
<small class="form-hint">${_('branding.logo_text_accent_hint')}</small>
</div>
</div>
<!-- Icon options -->
<div class="logo-config-panel" id="logo-icon-panel">
<div class="form-group">
<label class="form-label" for="logo-icon-name">${_('branding.logo_icon_name')}</label>
<input type="text" id="logo-icon-name" class="form-input"
placeholder="dns" maxlength="50">
<small class="form-hint">${_('branding.logo_icon_hint')}</small>
</div>
</div>
<!-- SVG options -->
<div class="logo-config-panel hidden" id="logo-svg-panel">
<div class="form-group">
<label class="form-label" for="logo-svg-input">${_('branding.logo_svg_code')}</label>
<textarea id="logo-svg-input" class="form-input form-textarea" rows="6"
placeholder="<svg>...</svg>"></textarea>
<small class="form-hint">${_('branding.logo_svg_hint')}</small>
</div>
</div>
<!-- Image options -->
<div class="logo-config-panel hidden" id="logo-image-panel">
<div class="form-group">
<label class="form-label">${_('branding.logo_image_upload')}</label>
<div class="logo-upload-area">
<input type="file" id="logo-image-file" accept="image/png,image/jpeg,image/gif,image/webp,image/svg+xml" class="logo-file-input">
<label for="logo-image-file" class="logo-upload-btn">
<span class="material-icons">upload_file</span>
<span>${_('branding.logo_image_choose')}</span>
</label>
<span id="logo-file-name" class="logo-file-name"></span>
</div>
<small class="form-hint">${_('branding.logo_image_hint')}</small>
</div>
<div class="form-group">
<label class="form-label" for="logo-image-url">${_('branding.logo_image_url')}</label>
<input type="text" id="logo-image-url" class="form-input"
placeholder="https://example.com/logo.png">
<small class="form-hint">${_('branding.logo_image_url_hint')}</small>
</div>
</div>
<!-- Logo preview -->
<div class="branding-preview-box">
<label class="form-label">${_('branding.preview')}</label>
<div class="logo-preview" id="logo-preview">
<span class="material-icons">dns</span>
<span class="logo-preview-text">BetterDesk</span>
</div>
</div>
</div>
</div>
</div>
<!-- Color Scheme -->
<div class="settings-panel branding-module-panel" data-branding-module="colors">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">palette</span>
${_('branding.colors_title')}
</h2>
<p class="settings-panel-desc">${_('branding.colors_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="color-grid">
<div class="color-group">
<h3 class="color-group-title">${_('branding.colors_backgrounds')}</h3>
<div class="color-row">
<label class="form-label">Primary</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="bgPrimary" value="#0d1117">
<input type="text" class="form-input color-hex" data-color="bgPrimary"
placeholder="#0d1117" maxlength="7">
</div>
</div>
<div class="color-row">
<label class="form-label">Secondary</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="bgSecondary" value="#161b22">
<input type="text" class="form-input color-hex" data-color="bgSecondary"
placeholder="#161b22" maxlength="7">
</div>
</div>
<div class="color-row">
<label class="form-label">Tertiary</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="bgTertiary" value="#21262d">
<input type="text" class="form-input color-hex" data-color="bgTertiary"
placeholder="#21262d" maxlength="7">
</div>
</div>
<div class="color-row">
<label class="form-label">Elevated</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="bgElevated" value="#30363d">
<input type="text" class="form-input color-hex" data-color="bgElevated"
placeholder="#30363d" maxlength="7">
</div>
</div>
</div>
<div class="color-group">
<h3 class="color-group-title">${_('branding.colors_text')}</h3>
<div class="color-row">
<label class="form-label">Primary</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="textPrimary" value="#e6edf3">
<input type="text" class="form-input color-hex" data-color="textPrimary"
placeholder="#e6edf3" maxlength="7">
</div>
</div>
<div class="color-row">
<label class="form-label">Secondary</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="textSecondary" value="#8b949e">
<input type="text" class="form-input color-hex" data-color="textSecondary"
placeholder="#8b949e" maxlength="7">
</div>
</div>
</div>
<div class="color-group">
<h3 class="color-group-title">${_('branding.colors_accents')}</h3>
<div class="color-row">
<label class="form-label">Blue (Primary Accent)</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="accentBlue" value="#58a6ff">
<input type="text" class="form-input color-hex" data-color="accentBlue"
placeholder="#58a6ff" maxlength="7">
</div>
</div>
<div class="color-row">
<label class="form-label">Green (Success)</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="accentGreen" value="#2ea44f">
<input type="text" class="form-input color-hex" data-color="accentGreen"
placeholder="#2ea44f" maxlength="7">
</div>
</div>
<div class="color-row">
<label class="form-label">Red (Danger)</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="accentRed" value="#f85149">
<input type="text" class="form-input color-hex" data-color="accentRed"
placeholder="#f85149" maxlength="7">
</div>
</div>
<div class="color-row">
<label class="form-label">Yellow (Warning)</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="accentYellow" value="#d29922">
<input type="text" class="form-input color-hex" data-color="accentYellow"
placeholder="#d29922" maxlength="7">
</div>
</div>
<div class="color-row">
<label class="form-label">Purple</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="accentPurple" value="#a371f7">
<input type="text" class="form-input color-hex" data-color="accentPurple"
placeholder="#a371f7" maxlength="7">
</div>
</div>
</div>
<div class="color-group">
<h3 class="color-group-title">${_('branding.colors_borders')}</h3>
<div class="color-row">
<label class="form-label">Primary</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="borderPrimary" value="#30363d">
<input type="text" class="form-input color-hex" data-color="borderPrimary"
placeholder="#30363d" maxlength="7">
</div>
</div>
<div class="color-row">
<label class="form-label">Secondary</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" data-color="borderSecondary" value="#21262d">
<input type="text" class="form-input color-hex" data-color="borderSecondary"
placeholder="#21262d" maxlength="7">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Typography -->
<div class="settings-panel branding-module-panel" data-branding-module="typography">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">text_format</span>
${_('branding.typography_title')}
</h2>
<p class="settings-panel-desc">${_('branding.typography_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="font-config">
<!-- Heading Font -->
<div class="font-slot" id="font-heading-slot">
<div class="font-slot-header">
<h3 class="font-slot-title">${_('branding.font_heading')}</h3>
<small class="form-hint">${_('branding.font_heading_hint')}</small>
</div>
<div class="font-picker-row">
<div class="font-search-wrapper">
<input type="text" id="font-heading-search" class="form-input font-search-input"
placeholder="${_('branding.font_search_placeholder')}" autocomplete="off">
<div class="font-dropdown hidden" id="font-heading-dropdown"></div>
</div>
<input type="hidden" id="font-heading-value">
<span class="font-current" id="font-heading-current">${_('branding.font_system_default')}</span>
<button class="btn btn-ghost btn-sm font-clear-btn hidden" id="font-heading-clear" title="${_('branding.font_clear')}">
<span class="material-icons">close</span>
</button>
</div>
<div class="font-preview" id="font-heading-preview">
<span class="font-preview-text" style="font-size: 1.5rem; font-weight: 600;">The quick brown fox jumps over the lazy dog</span>
</div>
</div>
<!-- Body Font -->
<div class="font-slot" id="font-body-slot">
<div class="font-slot-header">
<h3 class="font-slot-title">${_('branding.font_body')}</h3>
<small class="form-hint">${_('branding.font_body_hint')}</small>
</div>
<div class="font-picker-row">
<div class="font-search-wrapper">
<input type="text" id="font-body-search" class="form-input font-search-input"
placeholder="${_('branding.font_search_placeholder')}" autocomplete="off">
<div class="font-dropdown hidden" id="font-body-dropdown"></div>
</div>
<input type="hidden" id="font-body-value">
<span class="font-current" id="font-body-current">${_('branding.font_system_default')}</span>
<button class="btn btn-ghost btn-sm font-clear-btn hidden" id="font-body-clear" title="${_('branding.font_clear')}">
<span class="material-icons">close</span>
</button>
</div>
<div class="font-preview" id="font-body-preview">
<span class="font-preview-text">The quick brown fox jumps over the lazy dog. 0123456789</span>
</div>
</div>
<!-- Font source tabs -->
<div class="font-source-tabs" role="tablist">
<button type="button" class="font-source-tab active" data-font-source="google">${_('branding.font_google')}</button>
<button type="button" class="font-source-tab" data-font-source="local">${_('branding.font_local')}</button>
<button type="button" class="font-source-tab" data-font-source="upload">${_('branding.font_upload')}</button>
</div>
<div class="font-source-panel active" id="font-source-google"></div>
<div class="font-source-panel" id="font-source-local">
<div class="font-local-list" id="font-local-list"></div>
</div>
<div class="font-source-panel" id="font-source-upload">
<div class="font-upload-area">
<input type="file" id="font-upload-file" accept=".woff2,.ttf,font/woff2,font/ttf" class="font-upload-input">
<label for="font-upload-file" class="btn btn-secondary btn-sm">
<span class="material-icons">upload_file</span>
${_('branding.font_upload_choose')}
</label>
<input type="text" id="font-upload-name" class="form-input" placeholder="${_('branding.font_upload_name')}" maxlength="80">
</div>
</div>
<!-- Category filter -->
<div class="font-category-filter">
<button class="btn btn-ghost btn-sm font-cat-btn active" data-category="all">${_('branding.font_cat_all')}</button>
<button class="btn btn-ghost btn-sm font-cat-btn" data-category="sans-serif">Sans-Serif</button>
<button class="btn btn-ghost btn-sm font-cat-btn" data-category="serif">Serif</button>
<button class="btn btn-ghost btn-sm font-cat-btn" data-category="monospace">Mono</button>
<button class="btn btn-ghost btn-sm font-cat-btn" data-category="display">Display</button>
</div>
<!-- Downloaded fonts info -->
<div class="font-local-info" id="font-local-info">
<span class="material-icons">folder</span>
<span id="font-local-count">0</span> ${_('branding.font_downloaded_count')}
</div>
</div>
</div>
</div>
<!-- Background & Appearance -->
<div class="settings-panel branding-module-panel" data-branding-module="backgrounds">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">wallpaper</span>
${_('branding.background_title')}
</h2>
<p class="settings-panel-desc">${_('branding.background_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="branding-bg-config">
<div class="logo-type-selector">
<label class="logo-type-option">
<input type="radio" name="bg-type" value="none">
<span class="logo-type-card">
<span class="material-icons">block</span>
<span>${_('branding.bg_type_none')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="bg-type" value="color">
<span class="logo-type-card">
<span class="material-icons">format_color_fill</span>
<span>${_('branding.bg_type_color')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="bg-type" value="gradient">
<span class="logo-type-card">
<span class="material-icons">gradient</span>
<span>${_('branding.bg_type_gradient')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="bg-type" value="image">
<span class="logo-type-card">
<span class="material-icons">image</span>
<span>${_('branding.bg_type_image')}</span>
</span>
</label>
</div>
<div class="logo-config-panel hidden" id="bg-color-panel">
<div class="form-group">
<label class="form-label" for="bg-color">${_('branding.bg_color')}</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" id="bg-color-picker" value="#0d1117">
<input type="text" class="form-input color-hex" id="bg-color"
placeholder="#0d1117" maxlength="32">
</div>
</div>
</div>
<div class="logo-config-panel hidden" id="bg-gradient-panel">
<div class="form-group">
<label class="form-label" for="bg-gradient">${_('branding.bg_gradient')}</label>
<input type="text" id="bg-gradient" class="form-input"
placeholder="linear-gradient(135deg, #1e293b 0%, #0f172a 100%)" maxlength="400">
<small class="form-hint">${_('branding.bg_gradient_hint')}</small>
</div>
</div>
<div class="logo-config-panel hidden" id="bg-image-panel">
<div class="background-picker">
<div class="background-picker-head">
<div>
<label class="form-label">${_('desktop.wp_images')}</label>
<small class="form-hint">${_('branding.bg_image_hint')}</small>
</div>
<input type="file" id="bg-image-file" data-target="bg-image-url" accept="image/png,image/jpeg,image/webp,image/gif" class="logo-file-input branding-bg-file">
<label for="bg-image-file" class="logo-upload-btn">
<span class="material-icons">upload_file</span>
<span>${_('branding.bg_image_choose')}</span>
</label>
</div>
<div class="background-library" id="bg-image-library" aria-label="${_('desktop.wp_images')}"></div>
<span id="bg-file-name" class="logo-file-name background-current-file"></span>
<div class="branding-upload-status" id="bg-upload-status" hidden>
<div class="branding-upload-status-row">
<span class="material-icons branding-upload-status-icon">sync</span>
<span class="branding-upload-message"></span>
</div>
<div class="branding-upload-progress">
<div class="branding-upload-progress-fill"></div>
</div>
</div>
<details class="background-advanced-url">
<summary>${_('settings.tab_advanced')}</summary>
<div class="form-group">
<label class="form-label" for="bg-image-url">${_('branding.bg_image_url')}</label>
<input type="text" id="bg-image-url" class="form-input"
placeholder="https://example.com/wallpaper.jpg">
<small class="form-hint">${_('branding.bg_image_hint')}</small>
</div>
</details>
</div>
<div class="form-group">
<label class="form-label" for="bg-size">${_('branding.bg_size')}</label>
<select id="bg-size" class="form-input form-select">
<option value="cover">${_('branding.bg_size_cover')}</option>
<option value="contain">${_('branding.bg_size_contain')}</option>
<option value="auto">${_('branding.bg_size_auto')}</option>
</select>
</div>
</div>
<div class="form-group" id="bg-effects-group">
<label class="form-label" for="bg-blur">${_('branding.bg_blur')} (<span id="bg-blur-value">0</span>px)</label>
<input type="range" id="bg-blur" class="form-range" min="0" max="30" step="1" value="0">
</div>
<div class="form-group" id="bg-overlay-group">
<label class="form-label" for="bg-overlay">${_('branding.bg_overlay')} (<span id="bg-overlay-value">0</span>%)</label>
<input type="range" id="bg-overlay" class="form-range" min="0" max="90" step="5" value="0">
<small class="form-hint">${_('branding.bg_overlay_hint')}</small>
</div>
<div class="branding-glass-config">
<h3 class="branding-subsection-title">${_('branding.glass_title')}</h3>
<p class="form-hint branding-subsection-desc">${_('branding.glass_desc')}</p>
<div class="form-group">
<label class="form-check">
<input type="checkbox" id="glass-enabled" checked>
<span>${_('branding.glass_enabled')}</span>
</label>
</div>
<div class="form-group">
<label class="form-label" for="glass-color">${_('branding.glass_color')}</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" id="glass-color-picker" value="#161b22">
<input type="text" class="form-input color-hex" id="glass-color"
placeholder="#161b22" maxlength="7">
</div>
<small class="form-hint">${_('branding.glass_color_hint')}</small>
</div>
<div class="form-group">
<label class="form-label" for="glass-blur">${_('branding.glass_blur')} (<span id="glass-blur-value">16</span>px)</label>
<input type="range" id="glass-blur" class="form-range" min="0" max="40" step="1" value="16">
</div>
<div class="form-group">
<label class="form-label" for="glass-opacity">${_('branding.glass_opacity')} (<span id="glass-opacity-value">55</span>%)</label>
<input type="range" id="glass-opacity" class="form-range" min="0" max="100" step="5" value="55">
</div>
</div>
</div>
</div>
</div>
<!-- Login Page -->
<div class="settings-panel branding-module-panel" data-branding-module="pages">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">login</span>
${_('branding.login_title')}
</h2>
<p class="settings-panel-desc">${_('branding.login_desc')}</p>
</div>
<div class="settings-panel-body">
<form class="settings-form branding-form" style="max-width: 600px;">
<div class="form-group">
<label class="form-label" for="login-title">${_('branding.login_heading')}</label>
<input type="text" id="login-title" class="form-input" maxlength="80"
placeholder="${_('branding.login_heading_placeholder')}">
</div>
<div class="form-group">
<label class="form-label" for="login-subtitle">${_('branding.login_subheading')}</label>
<input type="text" id="login-subtitle" class="form-input" maxlength="160"
placeholder="${_('branding.login_subheading_placeholder')}">
</div>
</form>
<div class="branding-bg-config" style="margin-top: 16px;">
<div class="logo-type-selector">
<label class="logo-type-option">
<input type="radio" name="login-bg-type" value="inherit">
<span class="logo-type-card">
<span class="material-icons">sync</span>
<span>${_('branding.login_bg_inherit')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="login-bg-type" value="color">
<span class="logo-type-card">
<span class="material-icons">format_color_fill</span>
<span>${_('branding.bg_type_color')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="login-bg-type" value="gradient">
<span class="logo-type-card">
<span class="material-icons">gradient</span>
<span>${_('branding.bg_type_gradient')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="login-bg-type" value="image">
<span class="logo-type-card">
<span class="material-icons">image</span>
<span>${_('branding.bg_type_image')}</span>
</span>
</label>
</div>
<div class="logo-config-panel hidden" id="login-bg-color-panel">
<div class="form-group">
<label class="form-label" for="login-bg-color">${_('branding.bg_color')}</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" id="login-bg-color-picker" value="#0d1117">
<input type="text" class="form-input color-hex" id="login-bg-color"
placeholder="#0d1117" maxlength="32">
</div>
</div>
</div>
<div class="logo-config-panel hidden" id="login-bg-gradient-panel">
<div class="form-group">
<label class="form-label" for="login-bg-gradient">${_('branding.bg_gradient')}</label>
<input type="text" id="login-bg-gradient" class="form-input"
placeholder="linear-gradient(135deg, #1e293b 0%, #0f172a 100%)" maxlength="400">
</div>
</div>
<div class="logo-config-panel hidden" id="login-bg-image-panel">
<div class="form-group">
<label class="form-label">${_('branding.bg_image_upload')}</label>
<div class="logo-upload-area">
<input type="file" id="login-bg-image-file" data-target="login-bg-image-url" accept="image/png,image/jpeg,image/webp,image/gif" class="logo-file-input branding-bg-file">
<label for="login-bg-image-file" class="logo-upload-btn">
<span class="material-icons">upload_file</span>
<span>${_('branding.bg_image_choose')}</span>
</label>
<span id="login-bg-file-name" class="logo-file-name"></span>
</div>
<div class="branding-upload-status" id="login-bg-upload-status" hidden>
<div class="branding-upload-status-row">
<span class="material-icons branding-upload-status-icon">sync</span>
<span class="branding-upload-message"></span>
</div>
<div class="branding-upload-progress">
<div class="branding-upload-progress-fill"></div>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="login-bg-image-url">${_('branding.bg_image_url')}</label>
<input type="text" id="login-bg-image-url" class="form-input"
placeholder="https://example.com/login-bg.jpg">
</div>
</div>
<div class="form-group">
<label class="form-label" for="login-bg-overlay">${_('branding.bg_overlay')} (<span id="login-bg-overlay-value">0</span>%)</label>
<input type="range" id="login-bg-overlay" class="form-range" min="0" max="90" step="5" value="0">
</div>
</div>
</div>
</div>
<!-- Agent Download Page -->
<div class="settings-panel branding-module-panel" data-branding-module="pages">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">download</span>
${_('branding.agent_title')}
</h2>
<p class="settings-panel-desc">${_('branding.agent_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="branding-bg-config">
<div class="logo-type-selector">
<label class="logo-type-option">
<input type="radio" name="agent-bg-type" value="none">
<span class="logo-type-card">
<span class="material-icons">block</span>
<span>${_('branding.bg_type_none')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="agent-bg-type" value="color">
<span class="logo-type-card">
<span class="material-icons">format_color_fill</span>
<span>${_('branding.bg_type_color')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="agent-bg-type" value="gradient">
<span class="logo-type-card">
<span class="material-icons">gradient</span>
<span>${_('branding.bg_type_gradient')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="agent-bg-type" value="image">
<span class="logo-type-card">
<span class="material-icons">image</span>
<span>${_('branding.bg_type_image')}</span>
</span>
</label>
</div>
<div class="logo-config-panel hidden" id="agent-bg-color-panel">
<div class="form-group">
<label class="form-label" for="agent-bg-color">${_('branding.bg_color')}</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" id="agent-bg-color-picker" value="#f1f5f9">
<input type="text" class="form-input color-hex" id="agent-bg-color"
placeholder="#f1f5f9" maxlength="32">
</div>
</div>
</div>
<div class="logo-config-panel hidden" id="agent-bg-gradient-panel">
<div class="form-group">
<label class="form-label" for="agent-bg-gradient">${_('branding.bg_gradient')}</label>
<input type="text" id="agent-bg-gradient" class="form-input"
placeholder="linear-gradient(135deg, #eef2ff 0%, #e0e7ff 100%)" maxlength="400">
</div>
</div>
<div class="logo-config-panel hidden" id="agent-bg-image-panel">
<div class="form-group">
<label class="form-label">${_('branding.bg_image_upload')}</label>
<div class="logo-upload-area">
<input type="file" id="agent-bg-image-file" data-target="agent-bg-image-url" accept="image/png,image/jpeg,image/webp,image/gif" class="logo-file-input branding-bg-file">
<label for="agent-bg-image-file" class="logo-upload-btn">
<span class="material-icons">upload_file</span>
<span>${_('branding.bg_image_choose')}</span>
</label>
<span id="agent-bg-file-name" class="logo-file-name"></span>
</div>
<div class="branding-upload-status" id="agent-bg-upload-status" hidden>
<div class="branding-upload-status-row">
<span class="material-icons branding-upload-status-icon">sync</span>
<span class="branding-upload-message"></span>
</div>
<div class="branding-upload-progress">
<div class="branding-upload-progress-fill"></div>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="agent-bg-image-url">${_('branding.bg_image_url')}</label>
<input type="text" id="agent-bg-image-url" class="form-input"
placeholder="https://example.com/agent-bg.jpg">
</div>
</div>
<div class="form-group toggle-group" style="display:flex;align-items:center;gap:12px;margin-top:8px;">
<input type="checkbox" id="agent-show-powered" class="toggle-switch" checked>
<div>
<label class="form-label" for="agent-show-powered" style="margin-bottom:0;cursor:pointer;">${_('branding.agent_show_powered')}</label>
<small class="form-hint" style="display:block;margin-top:2px;">${_('branding.agent_show_powered_hint')}</small>
</div>
</div>
</div>
</div>
</div>
<!-- RdClient / Remote Dashboard -->
<div class="settings-panel branding-module-panel" data-branding-module="pages">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">connected_tv</span>
RdClient
</h2>
<p class="settings-panel-desc">${_('remote_dashboard.subtitle')}</p>
</div>
<div class="settings-panel-body">
<div class="branding-bg-config">
<div class="logo-type-selector">
<label class="logo-type-option">
<input type="radio" name="rdclient-bg-type" value="inherit">
<span class="logo-type-card">
<span class="material-icons">sync</span>
<span>${_('branding.login_bg_inherit')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="rdclient-bg-type" value="none">
<span class="logo-type-card">
<span class="material-icons">block</span>
<span>${_('branding.bg_type_none')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="rdclient-bg-type" value="color">
<span class="logo-type-card">
<span class="material-icons">format_color_fill</span>
<span>${_('branding.bg_type_color')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="rdclient-bg-type" value="gradient">
<span class="logo-type-card">
<span class="material-icons">gradient</span>
<span>${_('branding.bg_type_gradient')}</span>
</span>
</label>
<label class="logo-type-option">
<input type="radio" name="rdclient-bg-type" value="image">
<span class="logo-type-card">
<span class="material-icons">image</span>
<span>${_('branding.bg_type_image')}</span>
</span>
</label>
</div>
<div class="logo-config-panel hidden" id="rdclient-bg-color-panel">
<div class="form-group">
<label class="form-label" for="rdclient-bg-color">${_('branding.bg_color')}</label>
<div class="color-input-wrapper">
<input type="color" class="color-picker" id="rdclient-bg-color-picker" value="#0d1117">
<input type="text" class="form-input color-hex" id="rdclient-bg-color"
placeholder="#0d1117" maxlength="32">
</div>
</div>
</div>
<div class="logo-config-panel hidden" id="rdclient-bg-gradient-panel">
<div class="form-group">
<label class="form-label" for="rdclient-bg-gradient">${_('branding.bg_gradient')}</label>
<input type="text" id="rdclient-bg-gradient" class="form-input"
placeholder="linear-gradient(135deg, #0d1117 0%, #172554 100%)" maxlength="400">
</div>
</div>
<div class="logo-config-panel hidden" id="rdclient-bg-image-panel">
<div class="form-group">
<label class="form-label">${_('branding.bg_image_upload')}</label>
<div class="logo-upload-area">
<input type="file" id="rdclient-bg-image-file" data-target="rdclient-bg-image-url" accept="image/png,image/jpeg,image/webp,image/gif" class="logo-file-input branding-bg-file">
<label for="rdclient-bg-image-file" class="logo-upload-btn">
<span class="material-icons">upload_file</span>
<span>${_('branding.bg_image_choose')}</span>
</label>
<span id="rdclient-bg-file-name" class="logo-file-name"></span>
</div>
<div class="branding-upload-status" id="rdclient-bg-upload-status" hidden>
<div class="branding-upload-status-row">
<span class="material-icons branding-upload-status-icon">sync</span>
<span class="branding-upload-message"></span>
</div>
<div class="branding-upload-progress">
<div class="branding-upload-progress-fill"></div>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="rdclient-bg-image-url">${_('branding.bg_image_url')}</label>
<input type="text" id="rdclient-bg-image-url" class="form-input"
placeholder="https://example.com/rdclient-bg.jpg">
</div>
<div class="form-group">
<label class="form-label" for="rdclient-bg-size">${_('branding.bg_size')}</label>
<select id="rdclient-bg-size" class="form-input form-select">
<option value="cover">${_('branding.bg_size_cover')}</option>
<option value="contain">${_('branding.bg_size_contain')}</option>
<option value="auto">${_('branding.bg_size_auto')}</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label" for="rdclient-bg-overlay">${_('branding.bg_overlay')} (<span id="rdclient-bg-overlay-value">0</span>%)</label>
<input type="range" id="rdclient-bg-overlay" class="form-range" min="0" max="90" step="5" value="0">
<small class="form-hint">${_('branding.bg_overlay_hint')}</small>
</div>
</div>
</div>
</div>
<!-- Footer & Copyright -->
<div class="settings-panel branding-module-panel" data-branding-module="advanced">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">copyright</span>
${_('branding.footer_title')}
</h2>
<p class="settings-panel-desc">${_('branding.footer_desc')}</p>
</div>
<div class="settings-panel-body">
<form class="settings-form branding-form" style="max-width: 600px;">
<div class="form-group">
<label class="form-label" for="footer-text">${_('branding.footer_text')}</label>
<input type="text" id="footer-text" class="form-input" maxlength="200"
placeholder="© 2026 Your Company. All rights reserved.">
<small class="form-hint">${_('branding.footer_text_hint')}</small>
</div>
<div class="form-group toggle-group" style="display:flex;align-items:center;gap:12px;">
<input type="checkbox" id="show-powered" class="toggle-switch" checked>
<div>
<label class="form-label" for="show-powered" style="margin-bottom:0;cursor:pointer;">${_('branding.show_powered')}</label>
<small class="form-hint" style="display:block;margin-top:2px;">${_('branding.show_powered_hint')}</small>
</div>
</div>
</form>
</div>
</div>
<!-- Custom CSS -->
<div class="settings-panel branding-module-panel" data-branding-module="advanced">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">code</span>
${_('branding.custom_css_title')}
</h2>
<p class="settings-panel-desc">${_('branding.custom_css_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="form-group" style="max-width: 800px;">
<textarea id="custom-css" class="form-input form-textarea code-textarea" rows="10"
placeholder=".sidebar { box-shadow: 0 0 20px rgba(0,0,0,.4); }"></textarea>
<small class="form-hint">${_('branding.custom_css_hint')}</small>
</div>
</div>
</div>
<!-- Import / Export -->
<div class="settings-panel branding-module-panel" data-branding-module="advanced">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">swap_horiz</span>
${_('branding.import_export_title')}
</h2>
<p class="settings-panel-desc">${_('branding.import_export_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="branding-actions-row">
<button class="btn btn-secondary" id="branding-export-btn">
<span class="material-icons">download</span>
${_('branding.export_theme')}
</button>
<label class="btn btn-secondary">
<span class="material-icons">upload</span>
${_('branding.import_theme')}
<input type="file" id="branding-import-input" accept=".json" hidden>
</label>
<button class="btn btn-danger" id="branding-reset-btn">
<span class="material-icons">restart_alt</span>
${_('branding.reset_defaults')}
</button>
</div>
</div>
</div>
</div>
<aside class="branding-live-preview" id="branding-live-preview">
<div class="branding-live-preview-header">
<h3>${_('branding.live_preview')}</h3>
<label class="branding-preview-page-toggle">
<input type="checkbox" id="branding-preview-page-toggle">
<span>${_('branding.preview_apply_page')}</span>
</label>
</div>
<div class="branding-preview-mockup" id="branding-live-mockup">
<div class="branding-mockup-sidebar">
<div class="branding-mockup-logo" id="branding-mockup-logo">
<span class="material-icons">dns</span>
<span class="branding-mockup-title">BetterDesk</span>
</div>
<div class="branding-mockup-nav-item active"></div>
<div class="branding-mockup-nav-item"></div>
<div class="branding-mockup-nav-item"></div>
</div>
<div class="branding-mockup-main">
<div class="branding-mockup-card">
<h4 class="branding-mockup-heading">${_('branding.preview_card_title')}</h4>
<p class="branding-mockup-body">${_('branding.preview_card_body')}</p>
<div class="branding-mockup-actions">
<button type="button" class="branding-mockup-btn primary">${_('branding.preview_btn_primary')}</button>
<button type="button" class="branding-mockup-btn secondary">${_('branding.preview_btn_secondary')}</button>
</div>
</div>
</div>
</div>
</aside>
</div>
</div>
</div>
<!-- Authentication Tab (server.config permission required) -->
<div class="settings-tab-content" id="tab-auth" role="tabpanel" aria-labelledby="settings-tab-auth" ${canServerConfig ? '' : 'style="display:none"'}>
<nav class="settings-auth-subnav" role="tablist" aria-label="${_('settings.tab_auth')}">
<button type="button" class="settings-auth-subtab active" role="tab" aria-selected="true" data-auth-panel="enrollment" id="settings-auth-tab-enrollment">
<span class="material-icons" aria-hidden="true">how_to_reg</span>
${_('settings.ui.auth_sub_enrollment')}
</button>
<button type="button" class="settings-auth-subtab" role="tab" aria-selected="false" data-auth-panel="ldap" id="settings-auth-tab-ldap">
<span class="material-icons" aria-hidden="true">domain</span>
${_('settings.ui.auth_sub_ldap')}
</button>
<button type="button" class="settings-auth-subtab" role="tab" aria-selected="false" data-auth-panel="oidc" id="settings-auth-tab-oidc">
<span class="material-icons" aria-hidden="true">login</span>
${_('settings.ui.auth_sub_oidc')}
</button>
</nav>
<div class="settings-auth-panel active" id="auth-panel-enrollment" role="tabpanel" aria-labelledby="settings-auth-tab-enrollment">
<div class="settings-stack">
<!-- Device enrollment -->
<div class="settings-panel" id="enrollment-settings-section">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">how_to_reg</span>
${_('settings.enrollment_title')}
</h2>
<p class="settings-panel-desc">${_('settings.enrollment_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="settings-choice-grid settings-enrollment-modes" role="radiogroup" aria-label="${_('tokens.enrollment_mode')}">
<label class="settings-choice-card enrollment-option">
<input type="radio" name="settings_enrollment_mode" value="open">
<span class="settings-choice-card-inner">
<span class="material-icons settings-choice-card-icon">lock_open</span>
<span class="settings-choice-card-title option-label">${_('tokens.mode_open')}</span>
<span class="settings-choice-card-desc option-desc">${_('tokens.mode_open_desc')}</span>
</span>
</label>
<label class="settings-choice-card enrollment-option">
<input type="radio" name="settings_enrollment_mode" value="managed">
<span class="settings-choice-card-inner">
<span class="material-icons settings-choice-card-icon">supervisor_account</span>
<span class="settings-choice-card-title option-label">${_('tokens.mode_managed')}</span>
<span class="settings-choice-card-desc option-desc">${_('tokens.mode_managed_desc')}</span>
</span>
</label>
<label class="settings-choice-card enrollment-option">
<input type="radio" name="settings_enrollment_mode" value="locked">
<span class="settings-choice-card-inner">
<span class="material-icons settings-choice-card-icon">lock</span>
<span class="settings-choice-card-title option-label">${_('tokens.mode_locked')}</span>
<span class="settings-choice-card-desc option-desc">${_('tokens.mode_locked_desc')}</span>
</span>
</label>
</div>
<div class="settings-row">
<div class="settings-row-label">
<label class="form-label" for="enrollment-require-approval">${_('settings.enrollment_require_approval')}</label>
<p class="settings-row-hint">${_('settings.enrollment_require_approval_hint')}</p>
</div>
<div class="settings-row-control">
<input type="checkbox" id="enrollment-require-approval" class="toggle-switch">
</div>
</div>
<div class="settings-row">
<div class="settings-row-label">
<label class="form-label" for="enrollment-rich-approve">${_('settings.enrollment_rich_approve')}</label>
<p class="settings-row-hint">${_('settings.enrollment_rich_approve_hint')}</p>
</div>
<div class="settings-row-control">
<input type="checkbox" id="enrollment-rich-approve" class="toggle-switch">
</div>
</div>
<div class="settings-row">
<div class="settings-row-label">
<label class="form-label" for="enrollment-tag-picker">${_('settings.enrollment_tag_picker')}</label>
<p class="settings-row-hint">${_('settings.enrollment_tag_picker_hint')}</p>
</div>
<div class="settings-row-control">
<input type="checkbox" id="enrollment-tag-picker" class="toggle-switch">
</div>
</div>
<p class="form-hint" id="enrollment-pending-hint"></p>
<div class="form-actions">
<a href="/registrations" class="btn btn-secondary btn-sm">
<span class="material-icons">pending_actions</span>
${_('settings.enrollment_open_requests')}
</a>
</div>
</div>
</div>
</div>
</div>
<div class="settings-auth-panel" id="auth-panel-ldap" role="tabpanel" aria-labelledby="settings-auth-tab-ldap" hidden>
<div class="settings-stack">
<div class="settings-panel">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">domain</span>
${_('settings.ldap_title')}
</h2>
<p class="settings-panel-desc">${_('settings.ldap_desc')}</p>
</div>
<div class="settings-panel-body">
<form class="settings-form" id="ldap-form" style="max-width: 700px;">
<!-- Enable toggle -->
<div class="form-group" style="display: flex; align-items: center; gap: 12px;">
<input type="checkbox" id="ldap-enabled" class="toggle-switch">
<div>
<label class="form-label" for="ldap-enabled" style="margin-bottom: 0; cursor: pointer;">${_('settings.ldap_enabled')}</label>
<small class="form-hint" style="display: block; margin-top: 2px;">${_('settings.ldap_enabled_hint')}</small>
</div>
</div>
<div id="ldap-config-fields">
<!-- Server connection -->
<h3 class="settings-subsection-title">${_('settings.ldap_connection')}</h3>
<div class="form-row">
<div class="form-group" style="flex: 2;">
<label class="form-label" for="ldap-host">${_('settings.ldap_host')}</label>
<input type="text" id="ldap-host" class="form-input" placeholder="ldap.example.com">
</div>
<div class="form-group" style="flex: 1;">
<label class="form-label" for="ldap-port">${_('settings.ldap_port')}</label>
<input type="number" id="ldap-port" class="form-input" placeholder="389" min="1" max="65535">
</div>
</div>
<div class="form-group" style="display: flex; gap: 24px; flex-wrap: wrap;">
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
<input type="checkbox" id="ldap-use-tls" class="toggle-switch">
<span>${_('settings.ldap_use_tls')}</span>
</label>
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
<input type="checkbox" id="ldap-start-tls" class="toggle-switch">
<span>${_('settings.ldap_start_tls')}</span>
</label>
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
<input type="checkbox" id="ldap-skip-tls-verify" class="toggle-switch">
<span>${_('settings.ldap_skip_verify')}</span>
</label>
</div>
<div class="form-group">
<label class="form-label" for="ldap-conn-timeout">${_('settings.ldap_timeout')}</label>
<input type="number" id="ldap-conn-timeout" class="form-input" placeholder="10" min="1" max="120" style="max-width: 120px;">
<small class="form-hint">${_('settings.ldap_timeout_hint')}</small>
</div>
<!-- Bind mode -->
<h3 class="settings-subsection-title">${_('settings.ldap_bind_mode')}</h3>
<div class="form-group" style="display: flex; align-items: center; gap: 12px;">
<input type="checkbox" id="ldap-direct-bind" class="toggle-switch">
<div>
<label class="form-label" for="ldap-direct-bind" style="margin-bottom: 0; cursor: pointer;">${_('settings.ldap_direct_bind')}</label>
<small class="form-hint" style="display: block; margin-top: 2px;">${_('settings.ldap_direct_bind_hint')}</small>
</div>
</div>
<!-- Bind+Search fields (shown when direct_bind is off) -->
<div id="ldap-bind-search-fields">
<div class="form-group">
<label class="form-label" for="ldap-bind-dn">${_('settings.ldap_bind_dn')}</label>
<input type="text" id="ldap-bind-dn" class="form-input" placeholder="cn=readonly,dc=example,dc=com">
<small class="form-hint">${_('settings.ldap_bind_dn_hint')}</small>
</div>
<div class="form-group">
<label class="form-label" for="ldap-bind-password">${_('settings.ldap_bind_password')}</label>
<input type="password" id="ldap-bind-password" class="form-input" placeholder="••••••••">
</div>
<div class="form-group">
<label class="form-label" for="ldap-base-dn">${_('settings.ldap_base_dn')}</label>
<input type="text" id="ldap-base-dn" class="form-input" placeholder="dc=example,dc=com">
<small class="form-hint">${_('settings.ldap_base_dn_hint')}</small>
</div>
<div class="form-group">
<label class="form-label" for="ldap-user-filter">${_('settings.ldap_user_filter')}</label>
<input type="text" id="ldap-user-filter" class="form-input" placeholder="(sAMAccountName={{username}})">
<small class="form-hint">${_('settings.ldap_user_filter_hint')}</small>
</div>
</div>
<!-- Direct bind fields (shown when direct_bind is on) -->
<div id="ldap-direct-bind-fields" style="display: none;">
<div class="form-group">
<label class="form-label" for="ldap-direct-bind-dn">${_('settings.ldap_direct_bind_dn')}</label>
<input type="text" id="ldap-direct-bind-dn" class="form-input" placeholder="uid={{username}},ou=users,dc=example,dc=com">
<small class="form-hint">${_('settings.ldap_direct_bind_dn_hint')}</small>
</div>
</div>
<!-- User attributes -->
<h3 class="settings-subsection-title">${_('settings.ldap_user_attrs')}</h3>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="ldap-user-attr-id">${_('settings.ldap_attr_id')}</label>
<input type="text" id="ldap-user-attr-id" class="form-input" placeholder="sAMAccountName">
</div>
<div class="form-group">
<label class="form-label" for="ldap-user-attr-email">${_('settings.ldap_attr_email')}</label>
<input type="text" id="ldap-user-attr-email" class="form-input" placeholder="mail">
</div>
<div class="form-group">
<label class="form-label" for="ldap-user-attr-name">${_('settings.ldap_attr_name')}</label>
<input type="text" id="ldap-user-attr-name" class="form-input" placeholder="displayName">
</div>
</div>
<!-- Group mapping -->
<h3 class="settings-subsection-title">${_('settings.ldap_group_mapping')}</h3>
<div class="form-group">
<label class="form-label" for="ldap-group-base-dn">${_('settings.ldap_group_base_dn')}</label>
<input type="text" id="ldap-group-base-dn" class="form-input" placeholder="ou=groups,dc=example,dc=com">
<small class="form-hint">${_('settings.ldap_group_base_dn_hint')}</small>
</div>
<div class="form-group">
<label class="form-label" for="ldap-group-filter">${_('settings.ldap_group_filter')}</label>
<input type="text" id="ldap-group-filter" class="form-input" placeholder="(member={{dn}})">
</div>
<div class="form-group">
<label class="form-label" for="ldap-group-attr-name">${_('settings.ldap_group_attr_name')}</label>
<input type="text" id="ldap-group-attr-name" class="form-input" placeholder="cn">
</div>
<div class="form-group">
<label class="form-label" for="ldap-default-role">${_('settings.ldap_default_role')}</label>
<select id="ldap-default-role" class="form-input" style="max-width: 300px;">
<option value="viewer">${_('settings.ldap_role_viewer')}</option>
<option value="operator">${_('settings.ldap_role_operator')}</option>
<option value="admin">${_('settings.ldap_role_admin')}</option>
</select>
<small class="form-hint">${_('settings.ldap_default_role_hint')}</small>
</div>
<div class="form-group">
<label class="form-label" for="ldap-group-role-map">${_('settings.ldap_group_role_map')}</label>
<textarea id="ldap-group-role-map" class="form-input form-textarea" rows="4"
placeholder="CN=BetterDesk Admins,OU=Groups,DC=example,DC=com=admin|CN=Operators,OU=Groups,DC=example,DC=com=operator"></textarea>
<small class="form-hint">${_('settings.ldap_group_role_map_hint')}</small>
</div>
</div>
<div class="form-actions" style="display: flex; gap: 12px; margin-top: 16px;">
<button type="button" class="btn btn-secondary" id="ldap-test-btn">
<span class="material-icons">cable</span>
${_('settings.ldap_test_connection')}
</button>
<button type="submit" class="btn btn-primary">
<span class="material-icons">save</span>
${_('settings.ldap_save')}
</button>
</div>
<div id="ldap-test-result" style="margin-top: 12px;"></div>
</form>
</div>
</div>
</div>
</div>
<div class="settings-auth-panel" id="auth-panel-oidc" role="tabpanel" aria-labelledby="settings-auth-tab-oidc" hidden>
<div class="settings-stack">
<!-- OIDC / OAuth2 / SSO -->
<div class="settings-panel">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">key</span>
${_('settings.oidc_title')}
</h2>
<p class="settings-panel-desc">${_('settings.oidc_desc')}</p>
</div>
<div class="settings-panel-body">
<form class="settings-form" id="oidc-form" style="max-width: 700px;">
<!-- Enable toggle -->
<div class="form-group" style="display: flex; align-items: center; gap: 12px;">
<input type="checkbox" id="oidc-enabled" class="toggle-switch">
<div>
<label class="form-label" for="oidc-enabled" style="margin-bottom: 0; cursor: pointer;">${_('settings.oidc_enabled')}</label>
<small class="form-hint" style="display: block; margin-top: 2px;">${_('settings.oidc_enabled_hint')}</small>
</div>
</div>
<div id="oidc-config-fields">
<!-- Provider settings -->
<h3 class="settings-subsection-title">${_('settings.oidc_provider')}</h3>
<div class="form-group">
<label class="form-label" for="oidc-display-name">${_('settings.oidc_display_name')}</label>
<input type="text" id="oidc-display-name" class="form-input" placeholder="Azure AD / Okta / Google" style="max-width: 400px;">
<small class="form-hint">${_('settings.oidc_display_name_hint')}</small>
</div>
<div class="form-group">
<label class="form-label" for="oidc-issuer-url">${_('settings.oidc_issuer_url')}</label>
<input type="url" id="oidc-issuer-url" class="form-input" placeholder="https://login.microsoftonline.com/{tenant}/v2.0">
<small class="form-hint">${_('settings.oidc_issuer_url_hint')}</small>
</div>
<div class="form-row">
<div class="form-group" style="flex: 2;">
<label class="form-label" for="oidc-client-id">${_('settings.oidc_client_id')}</label>
<input type="text" id="oidc-client-id" class="form-input" placeholder="your-client-id">
</div>
<div class="form-group" style="flex: 2;">
<label class="form-label" for="oidc-client-secret">${_('settings.oidc_client_secret')}</label>
<input type="password" id="oidc-client-secret" class="form-input" placeholder="••••••••">
</div>
</div>
<div class="form-group">
<label class="form-label" for="oidc-redirect-url">${_('settings.oidc_redirect_url')}</label>
<input type="url" id="oidc-redirect-url" class="form-input" placeholder="http://your-server:21114/api/auth/oidc/callback">
<small class="form-hint">${_('settings.oidc_redirect_url_hint')}</small>
</div>
<div class="form-group">
<label class="form-label" for="oidc-scopes">${_('settings.oidc_scopes')}</label>
<input type="text" id="oidc-scopes" class="form-input" placeholder="openid profile email" style="max-width: 500px;">
</div>
<div class="form-group" style="display: flex; gap: 24px; flex-wrap: wrap;">
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
<input type="checkbox" id="oidc-use-pkce" class="toggle-switch">
<span>${_('settings.oidc_use_pkce')}</span>
</label>
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
<input type="checkbox" id="oidc-auto-discovery" class="toggle-switch" checked>
<span>${_('settings.oidc_auto_discovery')}</span>
</label>
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
<input type="checkbox" id="oidc-allow-signup" class="toggle-switch" checked>
<span>${_('settings.oidc_allow_signup')}</span>
</label>
</div>
<!-- Manual endpoints (shown when auto_discovery is off) -->
<div id="oidc-manual-endpoints" style="display: none;">
<h3 class="settings-subsection-title">${_('settings.oidc_manual_endpoints')}</h3>
<div class="form-group">
<label class="form-label" for="oidc-authorization-url">${_('settings.oidc_authorization_url')}</label>
<input type="url" id="oidc-authorization-url" class="form-input" placeholder="https://idp.example.com/authorize">
</div>
<div class="form-group">
<label class="form-label" for="oidc-token-url">${_('settings.oidc_token_url')}</label>
<input type="url" id="oidc-token-url" class="form-input" placeholder="https://idp.example.com/token">
</div>
<div class="form-group">
<label class="form-label" for="oidc-userinfo-url">${_('settings.oidc_userinfo_url')}</label>
<input type="url" id="oidc-userinfo-url" class="form-input" placeholder="https://idp.example.com/userinfo">
</div>
</div>
<!-- Claim mapping -->
<h3 class="settings-subsection-title">${_('settings.oidc_claim_mapping')}</h3>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="oidc-claim-username">${_('settings.oidc_claim_username')}</label>
<input type="text" id="oidc-claim-username" class="form-input" placeholder="preferred_username">
</div>
<div class="form-group">
<label class="form-label" for="oidc-claim-email">${_('settings.oidc_claim_email')}</label>
<input type="text" id="oidc-claim-email" class="form-input" placeholder="email">
</div>
<div class="form-group">
<label class="form-label" for="oidc-claim-name">${_('settings.oidc_claim_name')}</label>
<input type="text" id="oidc-claim-name" class="form-input" placeholder="name">
</div>
</div>
<div class="form-group">
<label class="form-label" for="oidc-claim-groups">${_('settings.oidc_claim_groups')}</label>
<input type="text" id="oidc-claim-groups" class="form-input" placeholder="groups" style="max-width: 300px;">
</div>
<!-- Role mapping -->
<h3 class="settings-subsection-title">${_('settings.oidc_role_mapping')}</h3>
<div class="form-group">
<label class="form-label" for="oidc-default-role">${_('settings.oidc_default_role')}</label>
<select id="oidc-default-role" class="form-input" style="max-width: 300px;">
<option value="viewer">${_('settings.ldap_role_viewer')}</option>
<option value="operator">${_('settings.ldap_role_operator')}</option>
<option value="admin">${_('settings.ldap_role_admin')}</option>
</select>
</div>
<div class="form-group">
<label class="form-label" for="oidc-group-role-map">${_('settings.oidc_group_role_map')}</label>
<textarea id="oidc-group-role-map" class="form-input form-textarea" rows="3"
placeholder="betterdesk-admins=admin&#10;betterdesk-operators=operator"></textarea>
<small class="form-hint">${_('settings.oidc_group_role_map_hint')}</small>
</div>
</div>
<div class="form-actions" style="display: flex; gap: 12px; margin-top: 16px;">
<button type="button" class="btn btn-secondary" id="oidc-test-btn">
<span class="material-icons">travel_explore</span>
${_('settings.oidc_test_discovery')}
</button>
<button type="submit" class="btn btn-primary">
<span class="material-icons">save</span>
${_('settings.oidc_save')}
</button>
</div>
<div id="oidc-test-result" style="margin-top: 12px;"></div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Backup & Restore Tab (server.config permission required) -->
<div class="settings-tab-content" id="tab-backup" role="tabpanel" aria-labelledby="settings-tab-backup" ${canServerConfig ? '' : 'style="display:none"'}>
<div class="settings-stack">
<!-- Backup Info -->
<div class="settings-panel">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">backup</span>
${_('backup.title')}
</h2>
<p class="settings-panel-desc">${_('backup.desc')}</p>
</div>
<div class="settings-panel-body">
<div class="backup-stats-grid" id="backup-stats">
<div class="backup-stat-card">
<span class="backup-stat-label">${_('backup.stat_users')}</span>
<span class="backup-stat-value" id="backup-stat-users">-</span>
</div>
<div class="backup-stat-card">
<span class="backup-stat-label">${_('backup.stat_settings')}</span>
<span class="backup-stat-value" id="backup-stat-settings">-</span>
</div>
<div class="backup-stat-card">
<span class="backup-stat-label">${_('backup.stat_folders')}</span>
<span class="backup-stat-value" id="backup-stat-folders">-</span>
</div>
<div class="backup-stat-card">
<span class="backup-stat-label">${_('backup.stat_groups')}</span>
<span class="backup-stat-value" id="backup-stat-groups">-</span>
</div>
<div class="backup-stat-card">
<span class="backup-stat-label">${_('backup.stat_strategies')}</span>
<span class="backup-stat-value" id="backup-stat-strategies">-</span>
</div>
<div class="backup-stat-card">
<span class="backup-stat-label">${_('backup.stat_backend')}</span>
<span class="backup-stat-value" id="backup-stat-backend">-</span>
</div>
<div class="backup-stat-card">
<span class="backup-stat-label">${_('backup.stat_database')}</span>
<span class="backup-stat-value" id="backup-stat-database">-</span>
</div>
<div class="backup-stat-card">
<span class="backup-stat-label">${_('backup.stat_keys')}</span>
<span class="backup-stat-value" id="backup-stat-keys">-</span>
</div>
<div class="backup-stat-card">
<span class="backup-stat-label">${_('backup.stat_env')}</span>
<span class="backup-stat-value" id="backup-stat-env">-</span>
</div>
<div class="backup-stat-card">
<span class="backup-stat-label">${_('backup.stat_uploads')}</span>
<span class="backup-stat-value" id="backup-stat-uploads">-</span>
</div>
</div>
<div class="restore-warning-box">
<span class="material-icons">warning</span>
<span>${_('backup.security_warning')}</span>
</div>
<div class="backup-actions">
<button class="btn btn-primary" id="backup-download-btn">
<span class="material-icons">download</span>
${_('backup.download')}
</button>
</div>
</div>
</div>
<!-- Restore -->
<div class="settings-panel">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">restore</span>
${_('backup.restore_title')}
</h2>
<p class="settings-panel-desc">${_('backup.restore_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="restore-options">
<label class="restore-option">
<input type="checkbox" id="restore-database" checked>
<span>${_('backup.opt_database')}</span>
<small class="restore-hint">${_('backup.opt_database_hint')}</small>
</label>
<label class="restore-option">
<input type="checkbox" id="restore-uploads" checked>
<span>${_('backup.opt_uploads')}</span>
<small class="restore-hint">${_('backup.opt_uploads_hint')}</small>
</label>
<label class="restore-option restore-option-danger">
<input type="checkbox" id="restore-secrets">
<span>${_('backup.opt_secrets')}</span>
<small class="restore-warning">${_('backup.opt_secrets_warning')}</small>
</label>
<label class="restore-option restore-option-danger">
<input type="checkbox" id="restore-env">
<span>${_('backup.opt_env')}</span>
<small class="restore-warning">${_('backup.opt_env_warning')}</small>
</label>
<label class="restore-option restore-option-danger">
<input type="checkbox" id="restore-godb">
<span>${_('backup.opt_godb')}</span>
<small class="restore-warning">${_('backup.opt_godb_warning')}</small>
</label>
</div>
<div class="restore-warning-box restore-warning-box-danger">
<span class="material-icons">priority_high</span>
<span>${_('backup.restore_warning')}</span>
</div>
<div class="backup-actions">
<label class="btn btn-warning" id="restore-upload-label">
<span class="material-icons">upload</span>
${_('backup.upload')}
<input type="file" id="restore-file-input" accept=".gz,.tar.gz,.json" hidden>
</label>
</div>
<div class="restore-result" id="restore-result" style="display:none;"></div>
</div>
</div>
</div>
</div>
<!-- Updates Tab (server.config permission required) -->
<div class="settings-tab-content" id="tab-updates" role="tabpanel" aria-labelledby="settings-tab-updates" ${canServerConfig ? '' : 'style="display:none"'}>
<div class="win11-updates">
<h1 class="win11-updates-title">${_('updates.title')}</h1>
<!-- Hero status (Windows 11 Update style) -->
<section class="win11-updates-hero" aria-labelledby="update-status-headline">
<div class="win11-updates-hero-icon" id="update-hero-icon" data-state="idle" aria-hidden="true">
<svg class="win11-updates-hero-svg" viewBox="0 0 48 48" width="72" height="72" focusable="false">
<circle cx="24" cy="24" r="20" fill="none" stroke="currentColor" stroke-width="2.5" opacity="0.25"/>
<path fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"
d="M24 8a16 16 0 0 1 14.5 9.2M24 40a16 16 0 0 1-14.5-9.2M8 24h6M34 24h6"/>
</svg>
<span class="win11-updates-hero-badge" id="update-hero-badge" hidden>
<span class="material-icons">priority_high</span>
</span>
</div>
<div class="win11-updates-hero-body">
<h2 class="win11-updates-hero-headline" id="update-status-headline">${_('updates.title')}</h2>
<p class="win11-updates-hero-desc" id="update-status-desc">${_('updates.desc')}</p>
<dl class="update-version-info win11-updates-meta">
<div class="update-version-row">
<dt class="update-label">${_('updates.current_version')}</dt>
<dd class="update-value" id="update-local-version">v${appVersion}</dd>
</div>
<div class="update-version-row">
<dt class="update-label">${_('updates.latest_version')}</dt>
<dd class="update-value" id="update-remote-version"><span class="text-muted">&mdash;</span></dd>
</div>
<div class="update-version-row" id="update-status-row" style="display:none;">
<dt class="update-label">${_('updates.status')}</dt>
<dd class="update-value" id="update-status-badge"></dd>
</div>
</dl>
</div>
<div class="win11-updates-hero-action">
<button class="btn win11-updates-primary-btn" id="update-check-btn" type="button">
<span class="material-icons">refresh</span>
${_('updates.check_now')}
</button>
</div>
</section>
<div class="win11-updates-alerts" id="update-alerts">
<!-- Docker image deployment -->
<div class="update-docker-info win11-updates-alert" id="update-docker-info" style="display:none;">
<div class="update-docker-icon">
<span class="material-icons">cloud_download</span>
</div>
<div class="update-docker-body">
<strong>${_('updates.docker_title')}</strong>
<p id="update-docker-desc">${_('updates.docker_desc')}</p>
<pre class="update-docker-commands" id="update-docker-commands"></pre>
<p class="update-docker-images" id="update-docker-images"></p>
</div>
</div>
<!-- Stale server binary warning -->
<div class="update-stale-warning win11-updates-alert" id="update-stale-warning" style="display:none;">
<div class="update-stale-icon">
<span class="material-icons">gpp_maybe</span>
</div>
<div class="update-stale-body">
<strong>${_('updates.server_stale_title')}</strong>
<p id="update-stale-detail">${_('updates.server_stale_desc')}</p>
<button class="btn btn-warning" id="update-rebuild-server-btn" type="button">
<span class="material-icons">build</span>
${_('updates.rebuild_server')}
</button>
</div>
</div>
</div>
<!-- Available update package (shown after check) -->
<section class="win11-updates-available" id="update-details-section" style="display:none;" aria-labelledby="update-available-heading">
<h2 class="win11-updates-available-title" id="update-available-heading">${_('updates.update_available')}</h2>
<div class="win11-updates-available-body">
<div class="update-changelog" id="update-changelog"></div>
<div class="update-files-summary" id="update-files-summary"></div>
<div class="win11-updates-install-row">
<label class="win11-updates-checkbox restore-option">
<input type="checkbox" id="update-backup-toggle" checked>
<span>${_('updates.create_backup')}</span>
</label>
<button class="btn win11-updates-primary-btn win11-updates-install-btn" id="update-install-btn" type="button" disabled>
<span class="material-icons">download</span>
${_('updates.install')}
</button>
<p class="update-docker-install-note text-muted" id="update-docker-install-note" style="display:none;">
${_('updates.docker_install_note')}
</p>
</div>
<div class="update-progress" id="update-progress" style="display:none;">
<div class="update-progress-bar">
<div class="update-progress-fill" id="update-progress-fill"></div>
</div>
<p class="update-progress-text" id="update-progress-text">${_('updates.installing')}</p>
</div>
</div>
</section>
<h2 class="win11-updates-section-title">${_('updates.more_options')}</h2>
<div class="win11-updates-options">
<!-- Update channel (inline control like Win11 pause dropdown) -->
<div class="win11-updates-option win11-updates-option-inline" id="update-channel-panel">
<div class="win11-updates-option-row">
<span class="win11-updates-option-icon material-icons" aria-hidden="true">alt_route</span>
<div class="win11-updates-option-text">
<span class="win11-updates-option-title">${_('updates.channel_title')}</span>
<span class="win11-updates-option-desc">${_('updates.channel_desc')}</span>
</div>
<select id="update-channel-select" class="win11-updates-select" aria-label="${_('updates.channel_pick_label')}">
<option value="stable">${_('updates.channel_stable')}</option>
<option value="development">${_('updates.channel_development')}</option>
</select>
</div>
<div class="win11-updates-option-meta">
<span id="update-channel-active-badge" class="badge badge-success badge-sm">&mdash;</span>
<span class="win11-updates-option-meta-sep" aria-hidden="true">&middot;</span>
<span class="text-muted">${_('updates.channel_branch_label')}:</span>
<span id="update-channel-branch">&mdash;</span>
<p class="win11-updates-option-hint">${_('updates.channel_apply_hint')}</p>
</div>
</div>
<!-- Update history / backups -->
<details class="win11-updates-option">
<summary class="win11-updates-option-row">
<span class="win11-updates-option-icon material-icons" aria-hidden="true">history</span>
<div class="win11-updates-option-text">
<span class="win11-updates-option-title">${_('updates.backups_title')}</span>
<span class="win11-updates-option-desc">${_('updates.backups_desc')}</span>
</div>
<span class="win11-updates-option-chevron material-icons" aria-hidden="true">chevron_right</span>
</summary>
<div class="win11-updates-option-body">
<div class="backup-retention-row">
<label for="backup-retention-input" class="backup-retention-label">
${_('updates.retention_label')}
<small class="text-muted">${_('updates.retention_hint')}</small>
</label>
<div class="backup-retention-controls">
<input type="number" id="backup-retention-input" min="0" max="1000" step="1" value="0">
<button class="btn btn-sm btn-primary" id="backup-retention-save" type="button">
${_('common.save')}
</button>
<button class="btn btn-sm btn-outline" id="backup-prune-now" type="button">
<span class="material-icons">cleaning_services</span>
${_('updates.prune_now')}
</button>
</div>
</div>
<div class="backup-summary text-muted" id="backup-summary"></div>
<div id="update-backups-list">
<p class="text-muted">${_('common.loading')}</p>
</div>
</div>
</details>
<!-- Advanced options -->
<details class="win11-updates-option">
<summary class="win11-updates-option-row">
<span class="win11-updates-option-icon material-icons" aria-hidden="true">tune</span>
<div class="win11-updates-option-text">
<span class="win11-updates-option-title">${_('updates.option_advanced')}</span>
<span class="win11-updates-option-desc">${_('updates.option_advanced_desc')}</span>
</div>
<span class="win11-updates-option-chevron material-icons" aria-hidden="true">chevron_right</span>
</summary>
<div class="win11-updates-option-body win11-updates-advanced-body">
<p class="text-muted win11-updates-advanced-note">${_('updates.auto_strategy_hint')}</p>
</div>
</details>
</div>
</div>
</div>
<!-- Email / SMTP Tab -->
<div class="settings-tab-content" id="tab-email" role="tabpanel" aria-labelledby="settings-tab-email" ${canServerConfig ? '' : 'style="display:none"'}>
<div class="settings-stack">
<div class="settings-panel">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">email</span>
${_('settings.email.title')}
</h2>
<p class="settings-panel-desc">${_('settings.email.desc')}</p>
</div>
<div class="settings-panel-body">
<form id="email-smtp-form" class="settings-form">
<div class="form-row">
<div class="form-group">
<label class="form-label" for="email-smtp-host">${_('settings.email.smtp_host')}</label>
<input type="text" id="email-smtp-host" class="form-input" placeholder="smtp.example.com">
</div>
<div class="form-group">
<label class="form-label" for="email-smtp-port">${_('settings.email.smtp_port')}</label>
<input type="number" id="email-smtp-port" class="form-input" value="587" min="1" max="65535">
</div>
</div>
<div class="form-group">
<label class="form-label">
<input type="checkbox" id="email-smtp-secure"> ${_('settings.email.smtp_secure')}
</label>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="email-smtp-user">${_('settings.email.smtp_user')}</label>
<input type="text" id="email-smtp-user" class="form-input">
</div>
<div class="form-group">
<label class="form-label" for="email-smtp-pass">${_('settings.email.smtp_pass')}</label>
<input type="password" id="email-smtp-pass" class="form-input" autocomplete="new-password">
</div>
</div>
<div class="form-group">
<label class="form-label" for="email-smtp-from">${_('settings.email.smtp_from')}</label>
<input type="email" id="email-smtp-from" class="form-input" placeholder="alerts@example.com">
</div>
<div class="form-group">
<label class="form-label" for="email-smtp-alert">${_('settings.email.alert_email')}</label>
<input type="email" id="email-smtp-alert" class="form-input" placeholder="ops@example.com">
<span class="form-hint">${_('settings.email.alert_email_hint')}</span>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" id="email-smtp-test-btn">${_('settings.email.smtp_test')}</button>
<button type="button" class="btn btn-primary" id="email-smtp-save-btn">${_('settings.email.smtp_save')}</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Advanced config files (server.config permission required) -->
<div class="settings-tab-content" id="tab-advanced" role="tabpanel" aria-labelledby="settings-tab-advanced" ${canServerConfig ? '' : 'style="display:none"'}>
<div class="settings-stack">
<div class="settings-panel">
<div class="settings-panel-header">
<h2 class="settings-panel-title">
<span class="material-icons">code</span>
${_('settings.advanced_title')}
</h2>
<p class="settings-panel-desc">${_('settings.advanced_desc')}</p>
</div>
<div class="settings-panel-body">
<div class="restore-warning-box">
<span class="material-icons">warning</span>
<span>${_('settings.advanced_warning')}</span>
</div>
<div class="advanced-config-layout">
<aside class="advanced-config-sidebar">
<p class="advanced-config-sidebar-label">${_('settings.advanced_files_label')}</p>
<ul class="advanced-config-file-list" id="advanced-config-file-list">
<li class="text-muted">${_('common.loading')}</li>
</ul>
</aside>
<div class="advanced-config-editor">
<div class="advanced-config-editor-toolbar" id="advanced-config-toolbar" hidden>
<div class="advanced-config-editor-meta">
<code class="advanced-config-path" id="advanced-config-path"></code>
<span class="text-muted" id="advanced-config-meta"></span>
</div>
<div class="advanced-config-editor-actions">
<button type="button" class="btn btn-sm btn-outline" id="advanced-config-reload">
<span class="material-icons">refresh</span>
${_('settings.advanced_reload')}
</button>
<button type="button" class="btn btn-sm btn-primary" id="advanced-config-save">
<span class="material-icons">save</span>
${_('settings.advanced_save')}
</button>
<button type="button" class="btn btn-sm btn-warning" id="advanced-config-restart" hidden>
<span class="material-icons">restart_alt</span>
${_('settings.advanced_apply_restart')}
</button>
</div>
</div>
<p class="advanced-config-placeholder text-muted" id="advanced-config-placeholder">${_('settings.advanced_select_file')}</p>
<textarea class="advanced-config-textarea" id="advanced-config-textarea" spellcheck="false" hidden></textarea>
<p class="advanced-config-restart-hint text-muted" id="advanced-config-restart-hint" hidden></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div><!-- .settings-shell-main -->
</div><!-- .settings-shell -->
`
}) %>