mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-11 13:49:03 +00:00
760c0e933d
Generator UI: web-nodejs/views/generator.ejs + public/js/generator.js + public/css/generator.css. Logo upload up to 10 MB, 16 MB body parser, branding form (product name, colors, server URL, etc.), per-branding hash deduplication.
Build pipeline: web-nodejs/services/agentBundleService.js (queue API + branding hash) and agentBuildWorker.js (DB-backed queue, 5s poll, concurrency 1, 30 min timeout). Spawns 'cargo tauri build --bundles <fmt> [--target <triple>] [--runner cargo-xwin]' per platform under systemd User=root. Loads /etc/betterdesk/build.env at module top so BUILD_USER/CARGO_HOME/PATH survive empty service env. Uses absolute CARGO_BIN/NPM_BIN paths to avoid PATH-resolution issues. Artifact path resolution honors profile.target presence (no triple subdir when omitted).
Toolchain installer: scripts/install-build-toolchain.sh (Rust + targets + cargo-tauri + cargo-xwin + mingw + makensis + dpkg-deb + rpmbuild + appimagetool + pnpm + node), writes /etc/betterdesk/build.env, 12-tool verification. Wired into betterdesk.sh menu as option B with post-install rsync of agent source to /opt/BetterDeskConsole/agent-source/.
Agent download page: web-nodejs/views/agent-download.ejs + public/css/agent-download.css for end-user installer downloads per platform/format with live status.
Branding scaffold (Tauri side): betterdesk-agent-client/src-tauri/src/branding.rs (Branding struct + OnceLock cache + BETTERDESK_AGENT_BRANDING env override + BaseDirectory::Resource resolve). resources/branding.json (dev skeleton). lib.rs registers module + get_branding command. commands.rs exposes get_branding IPC. tauri.conf.json declares resources/branding.json. Frontend integration of get_branding is intentionally pending — alpha.
Database: web-nodejs/services/database.js + dbAdapter.js add agent_bundle_builds + agent_bundles tables with full PostgreSQL + SQLite parity.
i18n: en.json + pl.json + zh-TW.json get ~75 new keys covering generator wizard, build status, download page, and toolchain installer messages.
Validated end-to-end on prod (Ubuntu 24.04, 4-core, PostgreSQL): linux/x64/AppImage built successfully (83.7 MB, 283s) for branding hash 25e2f242. linux/deb in progress, rpm + windows/exe queued.
Known follow-ups (NOT in this commit): SolidJS invoke('get_branding') wiring in App.tsx, betterdesk.ps1 toolchain menu mirror, reset-password.js PostgreSQL support, Docker decision.
This commit was made possible thanks to Insolve.
211 lines
13 KiB
HTML
211 lines
13 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>
|
|
</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>
|
|
|
|
<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">
|
|
<div class="form-group">
|
|
<label class="form-label" for="gen-lang">${_('generator.default_lang')}</label>
|
|
<select id="gen-lang" class="form-input">
|
|
<option value="en">English</option>
|
|
<option value="pl">Polski</option>
|
|
<option value="zh-TW">繁體中文</option>
|
|
</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">
|
|
<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-logo" id="gen-prev-logo">
|
|
<span class="material-icons">support_agent</span>
|
|
</div>
|
|
<div class="ap-brand">
|
|
<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-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-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>
|
|
`
|
|
}) %>
|