Files
BetterDesk/web-nodejs/views/settings.ejs
T
UNITRONIX 4d2a4e0488 fix(api,ui): fix tags/groups, status type, operator permissions (#138)
UI: group chip buttons changed to icon-only (matching folder chips), Create Group tile styling unified with Create Folder, old .group-chip-action CSS replaced with unified .chip-action class.

Go server: peerResponse and singlePeerResponse now return status as int (1=active, 0=disabled) instead of string, added status_text for admin panel backward compat. Fixes RustDesk client crash 'type String is not a subtype of type int?'.

Go server: new handleUsersWithClientFallback — detects RustDesk client requests to /api/users and returns current user without requiring user.view permission. Fixes disappearing folders/groups caused by _getUsers() 403 short-circuiting _pull().

Node.js: normalisePeer updated to use status_text fallback for status_tier.

Branding: RustDesk Server Management -> BetterDesk Server Management across themes, i18n, settings. Console version bumped to 3.0.0.

This commit was made possible thanks to Insolve.
2026-05-28 01:46:19 +02:00

782 lines
44 KiB
HTML

<%- include('layouts/main', {
title: _('nav.settings'),
pageStyles: ['pages'],
pageScripts: ['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>
<!-- Settings Tab Navigation -->
<div class="settings-tabs">
<button class="settings-tab active" data-tab="general">
<span class="material-icons">tune</span>
${_('settings.tab_general')}
</button>
<button class="settings-tab" data-tab="branding">
<span class="material-icons">palette</span>
${_('settings.tab_branding')}
</button>
${canServerConfig ? `<button class="settings-tab" data-tab="backup">
<span class="material-icons">backup</span>
${_('settings.tab_backup')}
</button>
<button class="settings-tab" data-tab="updates">
<span class="material-icons">system_update</span>
${_('updates.tab_title')}
</button>` : ''}
</div>
<!-- General Tab -->
<div class="settings-tab-content active" id="tab-general">
<div class="settings-sections">
<!-- Password Change Section -->
<div class="settings-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">lock</span>
${_('settings.change_password')}
</h2>
<p class="settings-section-desc">${_('settings.password_desc')}</p>
</div>
<div class="settings-section-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-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">info</span>
${_('settings.server_info')}
</h2>
</div>
<div class="settings-section-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">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>
<!-- Two-Factor Authentication Section -->
<div class="settings-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">security</span>
${_('settings.totp_title')}
</h2>
<p class="settings-section-desc">${_('settings.totp_desc')}</p>
</div>
<div class="settings-section-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-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">history</span>
${_('settings.audit_log')}
</h2>
<p class="settings-section-desc">${_('settings.audit_desc')}</p>
</div>
<div class="settings-section-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-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">school</span>
${_('settings.tutorials_title')}
</h2>
<p class="settings-section-desc">${_('settings.tutorials_desc')}</p>
</div>
<div class="settings-section-body">
<div class="form-group" style="max-width: 600px; display: flex; align-items: center; gap: 12px;">
<input type="checkbox" id="tutorials-enabled" class="toggle-switch">
<div>
<label class="form-label" for="tutorials-enabled" style="margin-bottom: 0; cursor: pointer;">${_('settings.tutorials_enabled')}</label>
<small class="form-hint" style="display: block; margin-top: 2px;">${_('settings.tutorials_enabled_hint')}</small>
</div>
</div>
<div class="form-group" style="max-width: 600px;">
<button class="btn btn-secondary btn-sm" id="tutorials-reset-btn">
<span class="material-icons">refresh</span>
${_('tutorial.reset_all')}
</button>
<small class="form-hint" style="display: block; margin-top: 6px;">${_('settings.tutorials_reset_hint')}</small>
</div>
</div>
</div>
</div>
</div>
<!-- Branding Tab -->
<div class="settings-tab-content" id="tab-branding">
<div class="settings-sections">
<!-- Brand Identity -->
<div class="settings-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">badge</span>
${_('branding.identity_title')}
</h2>
<p class="settings-section-desc">${_('branding.identity_desc')}</p>
</div>
<div class="settings-section-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-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">image</span>
${_('branding.logo_title')}
</h2>
<p class="settings-section-desc">${_('branding.logo_desc')}</p>
</div>
<div class="settings-section-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-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">palette</span>
${_('branding.colors_title')}
</h2>
<p class="settings-section-desc">${_('branding.colors_desc')}</p>
</div>
<div class="settings-section-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-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">text_format</span>
${_('branding.typography_title')}
</h2>
<p class="settings-section-desc">${_('branding.typography_desc')}</p>
</div>
<div class="settings-section-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>
<!-- 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>
<!-- Import / Export -->
<div class="settings-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">swap_horiz</span>
${_('branding.import_export_title')}
</h2>
<p class="settings-section-desc">${_('branding.import_export_desc')}</p>
</div>
<div class="settings-section-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>
<!-- Save Branding Button -->
<div class="branding-save-bar">
<button class="btn btn-primary btn-lg" id="branding-save-btn">
<span class="material-icons">save</span>
${_('branding.save_branding')}
</button>
</div>
</div>
</div>
<!-- Backup & Restore Tab (server.config permission required) -->
<div class="settings-tab-content" id="tab-backup" ${canServerConfig ? '' : 'style="display:none"'}>
<div class="settings-sections">
<!-- Backup Info -->
<div class="settings-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">backup</span>
${_('backup.title')}
</h2>
<p class="settings-section-desc">${_('backup.desc')}</p>
</div>
<div class="settings-section-body">
<div class="backup-stats" id="backup-stats">
<div class="backup-stat-row">
<span class="backup-stat-label">${_('backup.stat_users')}</span>
<span class="backup-stat-value" id="backup-stat-users">-</span>
</div>
<div class="backup-stat-row">
<span class="backup-stat-label">${_('backup.stat_settings')}</span>
<span class="backup-stat-value" id="backup-stat-settings">-</span>
</div>
<div class="backup-stat-row">
<span class="backup-stat-label">${_('backup.stat_folders')}</span>
<span class="backup-stat-value" id="backup-stat-folders">-</span>
</div>
<div class="backup-stat-row">
<span class="backup-stat-label">${_('backup.stat_groups')}</span>
<span class="backup-stat-value" id="backup-stat-groups">-</span>
</div>
<div class="backup-stat-row">
<span class="backup-stat-label">${_('backup.stat_strategies')}</span>
<span class="backup-stat-value" id="backup-stat-strategies">-</span>
</div>
<div class="backup-stat-row">
<span class="backup-stat-label">${_('backup.stat_backend')}</span>
<span class="backup-stat-value" id="backup-stat-backend">-</span>
</div>
</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-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">restore</span>
${_('backup.restore_title')}
</h2>
<p class="settings-section-desc">${_('backup.restore_desc')}</p>
</div>
<div class="settings-section-body">
<div class="restore-options">
<label class="restore-option">
<input type="checkbox" id="restore-settings" checked>
<span>${_('backup.opt_settings')}</span>
</label>
<label class="restore-option">
<input type="checkbox" id="restore-branding" checked>
<span>${_('backup.opt_branding')}</span>
</label>
<label class="restore-option">
<input type="checkbox" id="restore-folders" checked>
<span>${_('backup.opt_folders')}</span>
</label>
<label class="restore-option">
<input type="checkbox" id="restore-groups" checked>
<span>${_('backup.opt_groups')}</span>
</label>
<label class="restore-option">
<input type="checkbox" id="restore-addressbooks" checked>
<span>${_('backup.opt_addressbooks')}</span>
</label>
<label class="restore-option restore-option-danger">
<input type="checkbox" id="restore-users">
<span>${_('backup.opt_users')}</span>
<small class="restore-warning">${_('backup.opt_users_warning')}</small>
</label>
</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=".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" ${canServerConfig ? '' : 'style="display:none"'}>
<div class="settings-sections">
<!-- Version Check -->
<div class="settings-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">system_update</span>
${_('updates.title')}
</h2>
<p class="settings-section-desc">${_('updates.desc')}</p>
</div>
<div class="settings-section-body">
<div class="update-version-info">
<div class="update-version-row">
<span class="update-label">${_('updates.current_version')}</span>
<span class="update-value" id="update-local-version">v${appVersion}</span>
</div>
<div class="update-version-row">
<span class="update-label">${_('updates.latest_version')}</span>
<span class="update-value" id="update-remote-version">
<span class="text-muted">&mdash;</span>
</span>
</div>
<div class="update-version-row" id="update-status-row" style="display:none;">
<span class="update-label">${_('updates.status')}</span>
<span class="update-value" id="update-status-badge"></span>
</div>
</div>
<div class="update-actions" style="margin-top: 16px;">
<button class="btn btn-primary" id="update-check-btn">
<span class="material-icons">refresh</span>
${_('updates.check_now')}
</button>
</div>
</div>
</div>
<!-- Update Details (hidden until check completes) -->
<div class="settings-section" id="update-details-section" style="display:none;">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">description</span>
${_('updates.changelog')}
</h2>
</div>
<div class="settings-section-body">
<div class="update-changelog" id="update-changelog"></div>
<div class="update-files-summary" id="update-files-summary" style="margin-top:12px;"></div>
<div class="update-actions" style="margin-top: 16px;">
<label class="restore-option" style="margin-bottom: 12px;">
<input type="checkbox" id="update-backup-toggle" checked>
<span>${_('updates.create_backup')}</span>
</label>
<button class="btn btn-primary btn-lg" id="update-install-btn" disabled>
<span class="material-icons">download</span>
${_('updates.install')}
</button>
</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>
</div>
<!-- Update Backups -->
<div class="settings-section">
<div class="settings-section-header">
<h2 class="settings-section-title">
<span class="material-icons">history</span>
${_('updates.backups_title')}
</h2>
<p class="settings-section-desc">${_('updates.backups_desc')}</p>
</div>
<div class="settings-section-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">
${_('common.save')}
</button>
<button class="btn btn-sm btn-outline" id="backup-prune-now">
<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>
</div>
</div>
</div>
`
}) %>