Files
BetterDesk/web-nodejs/views/generator.ejs
T
UNITRONIX 2e01955f6c feat(rdclient): enhance desktop client with server validation, LAN discovery, and settings management
- Introduced server URL validation via `GET /api/bd/server-info` and `probe_server_url`.
- Added LAN discovery capabilities using UDP and optional mDNS for local network panel detection.
- Implemented a local settings window for managing URL, TLS settings, and user preferences.
- Enhanced dashboard with a unified sidebar and improved scrolling behavior.
- Updated documentation and pre-release checklist to reflect new features and requirements.
2026-06-14 21:12:41 +02:00

297 lines
19 KiB
HTML

<%- include('layouts/main', {
title: _('nav.generator'),
pageStyles: ['pages', 'generator'],
pageScripts: ['generator'],
currentPage: 'generator',
breadcrumb: [{ label: _('nav.generator') }],
body: `
<div class="page-header">
<h1 class="page-title">
<span class="material-icons">tune</span>
${_('generator.title')}
</h1>
<p class="page-subtitle">${_('generator.subtitle')}</p>
</div>
<div class="generator-layout">
<!-- Left column: bundle list -->
<aside class="generator-bundles card">
<div class="card-header">
<h2 class="card-title">
<span class="material-icons">inventory_2</span>
${_('generator.bundles_title')}
</h2>
<button id="gen-new-bundle" class="btn btn-primary btn-sm">
<span class="material-icons">add</span>
${_('generator.new_bundle')}
</button>
<button id="gen-new-rdclient" class="btn btn-secondary btn-sm" title="${_('generator.rdclient_new_bundle')}">
<span class="material-icons">connected_tv</span>
${_('generator.rdclient_tab')}
</button>
</div>
<div class="card-body">
<div id="gen-bundle-list" class="bundle-list">
<p class="text-muted">${_('generator.loading')}</p>
</div>
</div>
</aside>
<!-- Right column: editor + preview -->
<section class="generator-editor card">
<div class="card-header">
<h2 class="card-title" id="gen-editor-title">
<span class="material-icons">edit</span>
${_('generator.editor_title')}
</h2>
<div class="editor-actions">
<button id="gen-revoke-btn" class="btn btn-warning btn-sm hidden">
<span class="material-icons">block</span>
${_('generator.revoke')}
</button>
<button id="gen-delete-btn" class="btn btn-danger btn-sm hidden">
<span class="material-icons">delete</span>
${_('common.delete')}
</button>
<button id="gen-save-btn" class="btn btn-primary btn-sm" disabled>
<span class="material-icons">save</span>
${_('common.save')}
</button>
</div>
</div>
<div class="card-body">
<div id="gen-empty-state" class="empty-state">
<span class="material-icons">touch_app</span>
<p>${_('generator.empty_state')}</p>
</div>
<div id="gen-editor-form" class="editor-form hidden">
<div class="editor-grid">
<div class="editor-fields">
<div class="form-group">
<label class="form-label" for="gen-name">${_('generator.bundle_name')}</label>
<input type="text" id="gen-name" class="form-input" maxlength="100" required>
<p class="form-hint">${_('generator.bundle_name_hint')}</p>
</div>
<div class="form-group">
<label class="form-label" for="gen-slug">${_('generator.short_link')}</label>
<div class="download-link-row">
<span class="input-prefix">/d/</span>
<input type="text" id="gen-slug" class="form-input" maxlength="32"
pattern="[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?"
autocomplete="off" spellcheck="false">
</div>
<p class="form-hint">${_('generator.short_link_hint')}</p>
</div>
<h3 class="section-title">${_('generator.connection_section')}</h3>
<div class="form-group">
<label class="form-label" for="gen-server-host">${_('generator.server_host')}</label>
<input type="text" id="gen-server-host" class="form-input" maxlength="253"
placeholder="${_('generator.server_host_placeholder')}" required>
<p class="form-hint">${_('generator.server_host_hint')}</p>
</div>
<div class="form-row">
<div class="form-group form-group-checkbox">
<label class="form-label">
<input type="checkbox" id="gen-use-https" checked>
${_('generator.use_https')}
</label>
</div>
<div class="form-group">
<label class="form-label">${_('generator.enrollment_token')}</label>
<input type="text" id="gen-token-mask" class="form-input" readonly
placeholder="${_('generator.enrollment_token_pending')}">
<p class="form-hint">${_('generator.enrollment_token_hint')}</p>
</div>
</div>
<h3 class="section-title">${_('generator.branding_section')}</h3>
<div class="form-group">
<label class="form-label" for="gen-company">${_('generator.company_name')}</label>
<input type="text" id="gen-company" class="form-input" maxlength="100" required>
</div>
<div class="form-group">
<label class="form-label" for="gen-short-text">${_('generator.short_text')}</label>
<textarea id="gen-short-text" class="form-input" rows="2" maxlength="500"></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="gen-email">${_('generator.contact_email')}</label>
<input type="email" id="gen-email" class="form-input" maxlength="200">
</div>
<div class="form-group">
<label class="form-label" for="gen-phone">${_('generator.contact_phone')}</label>
<input type="text" id="gen-phone" class="form-input" maxlength="200">
</div>
</div>
<div class="form-group">
<label class="form-label" for="gen-url">${_('generator.contact_url')}</label>
<input type="url" id="gen-url" class="form-input" maxlength="200" placeholder="https://example.com">
</div>
<div class="form-group">
<label class="form-label" for="gen-logo">${_('generator.logo')}</label>
<input type="file" id="gen-logo" class="form-input" accept="image/png,image/jpeg,image/svg+xml,image/webp">
<p class="form-hint">${_('generator.logo_hint')}</p>
<button type="button" id="gen-logo-clear" class="btn btn-ghost btn-sm hidden">
<span class="material-icons">close</span>
${_('generator.logo_clear')}
</button>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="gen-primary">${_('generator.primary_color')}</label>
<input type="color" id="gen-primary" class="form-input form-color" value="#2563eb">
</div>
<div class="form-group">
<label class="form-label" for="gen-accent">${_('generator.accent_color')}</label>
<input type="color" id="gen-accent" class="form-input form-color" value="#1e293b">
</div>
</div>
<div class="form-row color-grid">
<div class="form-group">
<label class="form-label" for="gen-bg">${_('generator.background_color')}</label>
<input type="color" id="gen-bg" class="form-input form-color" value="#0f172a">
</div>
<div class="form-group">
<label class="form-label" for="gen-surface">${_('generator.surface_color')}</label>
<input type="color" id="gen-surface" class="form-input form-color" value="#1e293b">
</div>
<div class="form-group">
<label class="form-label" for="gen-text">${_('generator.text_color')}</label>
<input type="color" id="gen-text" class="form-input form-color" value="#e2e8f0">
</div>
<div class="form-group">
<label class="form-label" for="gen-text-muted">${_('generator.text_muted_color')}</label>
<input type="color" id="gen-text-muted" class="form-input form-color" value="#94a3b8">
</div>
<div class="form-group">
<label class="form-label" for="gen-status-ready">${_('generator.status_ready_color')}</label>
<input type="color" id="gen-status-ready" class="form-input form-color" value="#22c55e">
</div>
<div class="form-group">
<label class="form-label" for="gen-header-text">${_('generator.header_text_color')}</label>
<input type="color" id="gen-header-text" class="form-input form-color" value="#ffffff">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="gen-lang">${_('generator.default_lang')}</label>
<select id="gen-lang" class="form-input">
${(typeof supportedLangs !== 'undefined' ? supportedLangs : ['en', 'pl', 'zh-TW']).map(function(code) {
const label = (typeof localeLabels !== 'undefined' && localeLabels[code]) ? localeLabels[code] : code;
return '<option value="' + code + '">' + label + '</option>';
}).join('')}
</select>
</div>
<div class="form-group form-group-checkbox">
<label class="form-label">
<input type="checkbox" id="gen-unattended">
${_('generator.allow_unattended')}
</label>
<p class="form-hint">${_('generator.allow_unattended_hint')}</p>
</div>
</div>
<div id="gen-download-info" class="download-info hidden">
<div class="builds-section" id="gen-builds-section">
<div class="builds-section-header">
<h3 class="section-title">${_('generator.builds_title')}</h3>
<button type="button" id="gen-rebuild-btn" class="btn btn-secondary btn-sm">
<span class="material-icons">sync</span>
${_('generator.rebuild_all')}
</button>
</div>
<p class="form-hint">${_('generator.builds_hint')}</p>
<div id="gen-builds-summary" class="builds-summary hidden"></div>
<div id="gen-builds-list" class="builds-list">
<p class="text-muted">${_('generator.builds_loading')}</p>
</div>
</div>
<h3 class="section-title">${_('generator.download_link_title')}</h3>
<div class="download-link-row">
<input type="text" id="gen-download-url" class="form-input" readonly>
<button type="button" id="gen-copy-link" class="btn btn-secondary">
<span class="material-icons">content_copy</span>
${_('copy')}
</button>
<a id="gen-open-link" class="btn btn-ghost" target="_blank" rel="noopener">
<span class="material-icons">open_in_new</span>
${_('generator.open_portal')}
</a>
</div>
<p class="form-hint">${_('generator.download_link_hint')}</p>
</div>
</div>
<!-- Live preview -->
<div class="editor-preview">
<h3 class="section-title">${_('generator.preview_title')}</h3>
<p class="form-hint">${_('generator.preview_hint')}</p>
<div class="agent-preview" id="gen-preview">
<div class="agent-preview-frame" style="--brand-primary: #2563eb; --brand-accent: #1e293b;">
<div class="ap-header">
<div class="ap-brand ap-brand-only">
<div class="ap-brand-name" id="gen-prev-name">${_('generator.preview_default_name')}</div>
<div class="ap-brand-text" id="gen-prev-text"></div>
</div>
</div>
<div class="ap-body">
<div class="ap-body-logo" id="gen-prev-body-logo">
<span class="material-icons">support_agent</span>
</div>
<div class="ap-status">
<span class="ap-status-dot"></span>
<span class="ap-status-text">${_('generator.preview_status_ready')}</span>
</div>
<div class="ap-id-row">
<span class="ap-id-label">${_('generator.preview_id')}</span>
<span class="ap-id-value">123 456 789</span>
</div>
<div class="ap-pw-row" id="gen-prev-pw-row">
<span class="ap-id-label">${_('generator.preview_password')}</span>
<span class="ap-id-value ap-pw">a B 3 # K 9</span>
</div>
<button class="ap-help" type="button" disabled>
<span class="material-icons">contact_support</span>
${_('generator.preview_help')}
</button>
<button class="ap-chat" type="button" disabled>
<span class="material-icons">chat</span>
${_('generator.preview_chat')}
</button>
</div>
<div class="ap-footer">
<button class="ap-icon-btn" type="button" disabled title="${_('generator.preview_settings')}">
<span class="material-icons">settings</span>
</button>
<div class="ap-contact" id="gen-prev-contact"></div>
<button class="ap-icon-btn" type="button" disabled title="${_('generator.preview_quit')}">
<span class="material-icons">power_settings_new</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div id="gen-validation-errors" class="validation-errors hidden"></div>
</div>
</div>
</section>
</div>
`
}) %>