diff --git a/web-nodejs/lang/en.json b/web-nodejs/lang/en.json index 0b979964..368d7713 100644 --- a/web-nodejs/lang/en.json +++ b/web-nodejs/lang/en.json @@ -2614,13 +2614,13 @@ "title": "Software Updates", "desc": "Check for new versions and update the BetterDesk console from GitHub", "current_version": "Installed version", - "latest_version": "Latest version", + "latest_version": "Latest commit", "status": "Status", "check_now": "Check for updates", "checking": "Checking…", "up_to_date": "Up to date", "update_available": "Update available", - "changelog": "Release Notes", + "changelog": "Recent Commits", "no_changelog": "No release notes available", "files_changed": "Files changed", "files": "files", @@ -2642,6 +2642,21 @@ "no_backups": "No update backups found", "restore": "Restore", "restore_confirm": "Are you sure you want to restore from this backup? Changed files will be overwritten.", - "restore_success": "Backup restored successfully. Console is restarting…" + "restore_success": "Backup restored successfully. Console is restarting…", + "baseline_set": "Baseline set", + "commits_behind": "commits behind", + "total_files": "Total files", + "applied": "Applied", + "failed": "Failed", + "skipped": "Skipped", + "removed": "Removed", + "auto": "Auto", + "manual": "Manual", + "component_console": "Web Console", + "component_server": "Go Server", + "component_agent": "Agent", + "component_scripts": "Scripts & Docker", + "component_other": "Other", + "server_rebuild_warning": "Go Server source changed — binary rebuild required. Use the ALL-IN-ONE script or compile manually." } } diff --git a/web-nodejs/lang/pl.json b/web-nodejs/lang/pl.json index 0584ff73..33b0e689 100644 --- a/web-nodejs/lang/pl.json +++ b/web-nodejs/lang/pl.json @@ -2614,13 +2614,13 @@ "title": "Aktualizacje oprogramowania", "desc": "Sprawdź nowe wersje i zaktualizuj konsolę BetterDesk z repozytorium GitHub", "current_version": "Zainstalowana wersja", - "latest_version": "Najnowsza wersja", + "latest_version": "Najnowszy commit", "status": "Status", "check_now": "Sprawdź aktualizacje", "checking": "Sprawdzanie…", "up_to_date": "Aktualna wersja", "update_available": "Dostępna aktualizacja", - "changelog": "Informacje o wydaniu", + "changelog": "Ostatnie commity", "no_changelog": "Brak informacji o wydaniu", "files_changed": "Zmienione pliki", "files": "plików", @@ -2642,6 +2642,21 @@ "no_backups": "Brak kopii zapasowych aktualizacji", "restore": "Przywróć", "restore_confirm": "Czy na pewno chcesz przywrócić z tej kopii zapasowej? Zmienione pliki zostaną nadpisane.", - "restore_success": "Kopia zapasowa przywrócona pomyślnie. Konsola jest restartowana…" + "restore_success": "Kopia zapasowa przywrócona pomyślnie. Konsola jest restartowana…", + "baseline_set": "Punkt odniesienia ustawiony", + "commits_behind": "commitów do tyłu", + "total_files": "Łączna liczba plików", + "applied": "Zastosowane", + "failed": "Nieudane", + "skipped": "Pominięte", + "removed": "Usunięte", + "auto": "Auto", + "manual": "Ręcznie", + "component_console": "Konsola Web", + "component_server": "Serwer Go", + "component_agent": "Agent", + "component_scripts": "Skrypty i Docker", + "component_other": "Inne", + "server_rebuild_warning": "Źródła serwera Go uległy zmianie — wymagana rekompilacja. Użyj skryptu ALL-IN-ONE lub skompiluj ręcznie." } } diff --git a/web-nodejs/lang/zh.json b/web-nodejs/lang/zh.json index ef19672f..978df2e7 100644 --- a/web-nodejs/lang/zh.json +++ b/web-nodejs/lang/zh.json @@ -2621,13 +2621,13 @@ "title": "Software Updates", "desc": "Check for new versions and update the BetterDesk console from GitHub", "current_version": "Installed version", - "latest_version": "Latest version", + "latest_version": "最新提交", "status": "Status", "check_now": "Check for updates", "checking": "Checking…", "up_to_date": "Up to date", "update_available": "Update available", - "changelog": "Release Notes", + "changelog": "最近提交", "no_changelog": "No release notes available", "files_changed": "Files changed", "files": "files", @@ -2649,6 +2649,21 @@ "no_backups": "No update backups found", "restore": "Restore", "restore_confirm": "Are you sure you want to restore from this backup? Changed files will be overwritten.", - "restore_success": "Backup restored successfully. Console is restarting…" + "restore_success": "Backup restored successfully. Console is restarting…", + "baseline_set": "基线已设置", + "commits_behind": "个提交落后", + "total_files": "总文件数", + "applied": "已应用", + "failed": "失败", + "skipped": "跳过", + "removed": "已删除", + "auto": "自动", + "manual": "手动", + "component_console": "Web控制台", + "component_server": "Go服务器", + "component_agent": "代理程序", + "component_scripts": "脚本和Docker", + "component_other": "其他", + "server_rebuild_warning": "Go服务器源代码已更改——需要重新编译。使用ALL-IN-ONE脚本或手动编译。" } } diff --git a/web-nodejs/public/css/pages.css b/web-nodejs/public/css/pages.css index bf245af5..efff5745 100644 --- a/web-nodejs/public/css/pages.css +++ b/web-nodejs/public/css/pages.css @@ -1348,3 +1348,102 @@ .update-backup-info .text-muted { font-size: var(--font-size-xs, 0.75rem); } + +/* commit list */ +.update-commits { + display: flex; + flex-direction: column; + gap: 2px; +} + +.update-commit-item { + display: flex; + align-items: baseline; + gap: var(--space-sm); + padding: 4px 0; + font-size: var(--font-size-sm); + line-height: 1.4; +} + +.update-commit-sha { + flex-shrink: 0; + font-size: 11px; + padding: 1px 6px; + background: var(--bg-tertiary, rgba(255,255,255,0.06)); + border-radius: 4px; + color: var(--primary); + font-family: var(--font-mono, 'Fira Code', monospace); +} + +.update-commit-msg { + flex: 1; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.update-commit-meta { + flex-shrink: 0; + font-size: 11px; + color: var(--text-muted, var(--text-secondary)); +} + +/* component breakdown */ +.update-components { + display: flex; + flex-direction: column; + gap: var(--space-xs); +} + +.update-component-row { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: 6px var(--space-sm); + background: var(--bg-tertiary, rgba(255,255,255,0.03)); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm, 6px); +} + +.update-component-row .material-icons { + font-size: 18px; + color: var(--text-secondary); +} + +.update-component-label { + flex: 1; + font-size: var(--font-size-sm); + color: var(--text-primary); + font-weight: 500; +} + +.update-component-count { + font-size: var(--font-size-xs, 0.75rem); + color: var(--text-secondary); +} + +.badge-sm { + font-size: 10px; + padding: 1px 6px; +} + +/* server rebuild warning */ +.update-warning { + display: flex; + align-items: center; + gap: var(--space-sm); + margin-top: var(--space-sm); + padding: var(--space-sm) var(--space-md); + background: rgba(255, 171, 0, 0.08); + border: 1px solid rgba(255, 171, 0, 0.25); + border-radius: var(--radius-md); + font-size: var(--font-size-sm); + color: #ffab00; + line-height: 1.4; +} + +.update-warning .material-icons { + font-size: 18px; + flex-shrink: 0; +} diff --git a/web-nodejs/public/js/settings.js b/web-nodejs/public/js/settings.js index 5b6d9bb1..d35cc98a 100644 --- a/web-nodejs/public/js/settings.js +++ b/web-nodejs/public/js/settings.js @@ -955,7 +955,7 @@ // ==================== Self-Update ==================== - let _updateState = { remoteVersion: null, changedFiles: [] }; + let _updateState = { remoteSHA: null, changedData: null }; function initUpdateSection() { const checkBtn = document.getElementById('update-check-btn'); @@ -984,47 +984,33 @@ try { const data = await Utils.api('/api/settings/updates/check'); - if (remoteEl) remoteEl.textContent = 'v' + (data.remoteVersion || '?'); + // Show commit SHA + message + if (remoteEl) { + const sha = data.remoteSHA ? data.remoteSHA.slice(0, 7) : '—'; + remoteEl.textContent = sha; + if (data.latestMessage) remoteEl.title = data.latestMessage; + } if (statusRow) statusRow.style.display = ''; - if (data.updateAvailable) { - if (statusBadge) statusBadge.innerHTML = `${_('updates.update_available')}`; + if (data.baselineEstablished) { + if (statusBadge) statusBadge.innerHTML = `${_('updates.baseline_set')}`; + if (detailsSection) detailsSection.style.display = 'none'; + if (installBtn) installBtn.disabled = true; + } else if (data.updateAvailable) { + const behind = data.commitsBehind > 0 ? ` (${data.commitsBehind} ${_('updates.commits_behind')})` : ''; + if (statusBadge) statusBadge.innerHTML = `${_('updates.update_available')}${behind}`; + + _updateState.remoteSHA = data.remoteSHA; // Fetch changed files - const localVer = data.localVersion; - const remoteVer = data.remoteVersion; - _updateState.remoteVersion = remoteVer; - try { - const changes = await Utils.api(`/api/settings/updates/changes?local=v${localVer}&remote=v${remoteVer}`); - _updateState.changedFiles = changes.files || []; - - const changelogEl = document.getElementById('update-changelog'); - const summaryEl = document.getElementById('update-files-summary'); - - if (changelogEl) { - if (data.releaseNotes) { - changelogEl.innerHTML = Utils.escapeHtml(data.releaseNotes).replace(/\n/g, '
'); - } else { - changelogEl.innerHTML = `

${_('updates.no_changelog')}

`; - } - } - - if (summaryEl) { - const added = _updateState.changedFiles.filter(f => f.status === 'added').length; - const modified = _updateState.changedFiles.filter(f => f.status === 'modified').length; - const removed = _updateState.changedFiles.filter(f => f.status === 'removed' || f.status === 'renamed').length; - summaryEl.innerHTML = `

folder ` + - `${_('updates.files_changed')}: ${_updateState.changedFiles.length} ` + - `(+${added} / ~${modified} / -${removed})

`; - } - + const changes = await Utils.api(`/api/settings/updates/changes?sha=${data.remoteSHA}`); + _updateState.changedData = changes; + renderUpdateDetails(data, changes); if (installBtn) installBtn.disabled = false; - } catch (e) { - if (detailsSection) { - const changelogEl = document.getElementById('update-changelog'); - if (changelogEl) changelogEl.innerHTML = `

${_('updates.changes_unavailable')}

`; - } + } catch (_e) { + const cl = document.getElementById('update-changelog'); + if (cl) cl.innerHTML = `

${_('updates.changes_unavailable')}

`; if (installBtn) installBtn.disabled = false; } @@ -1042,13 +1028,73 @@ } } + function renderUpdateDetails(checkData, changesData) { + const changelogEl = document.getElementById('update-changelog'); + const summaryEl = document.getElementById('update-files-summary'); + + // ---- Recent commits ---- + if (changelogEl) { + const commits = changesData.commits || []; + if (commits.length > 0) { + let html = '
'; + for (const c of commits.slice(0, 20)) { + const d = c.date ? new Date(c.date).toLocaleDateString() : ''; + html += `
+ ${Utils.escapeHtml(c.sha || '')} + ${Utils.escapeHtml(c.message || '')} + ${Utils.escapeHtml(c.author || '')} · ${d} +
`; + } + html += '
'; + changelogEl.innerHTML = html; + } else { + changelogEl.innerHTML = `

${_('updates.no_changelog')}

`; + } + } + + // ---- Component breakdown ---- + if (summaryEl) { + const grouped = changesData.grouped || {}; + const meta = { + console: { icon: 'web', label: _('updates.component_console'), auto: true }, + server: { icon: 'dns', label: _('updates.component_server'), auto: false }, + agent: { icon: 'smart_toy', label: _('updates.component_agent'), auto: false }, + scripts: { icon: 'terminal', label: _('updates.component_scripts'), auto: true }, + other: { icon: 'folder', label: _('updates.component_other'), auto: false } + }; + + let html = '
'; + for (const [comp, files] of Object.entries(grouped)) { + if (!files || files.length === 0) continue; + const m = meta[comp] || meta.other; + const badge = m.auto + ? `${_('updates.auto')}` + : `${_('updates.manual')}`; + html += `
+ ${m.icon} + ${m.label} + ${files.length} ${_('updates.files')} + ${badge} +
`; + } + html += '
'; + html += `

${_('updates.total_files')}: ${changesData.totalFiles || 0}

`; + + if (grouped.server?.length > 0) { + html += `
warning ${_('updates.server_rebuild_warning')}
`; + } + + summaryEl.innerHTML = html; + } + } + async function installUpdate() { const installBtn = document.getElementById('update-install-btn'); const progressEl = document.getElementById('update-progress'); const progressFill = document.getElementById('update-progress-fill'); const progressText = document.getElementById('update-progress-text'); - if (!_updateState.remoteVersion) { + if (!_updateState.remoteSHA) { Notifications.error(_('updates.no_version')); return; } @@ -1069,21 +1115,27 @@ const result = await Utils.api('/api/settings/updates/install', { method: 'POST', body: { - remoteVersion: _updateState.remoteVersion, - createBackup: createBackup + remoteSHA: _updateState.remoteSHA, + createBackup, + components: ['console', 'scripts'] } }); if (progressFill) progressFill.style.width = '100%'; - if (progressText) progressText.textContent = _('updates.restarting'); - Notifications.success(_('updates.install_success')); - - // Server will restart — poll until it comes back - setTimeout(() => { - pollServerRestart(); - }, 3000); + let msg = `${_('updates.applied')}: ${result.applied?.length || 0}`; + if (result.failed?.length) msg += `, ${_('updates.failed')}: ${result.failed.length}`; + if (result.removed?.length) msg += `, ${_('updates.removed')}: ${result.removed.length}`; + if (result.needsConsoleRestart) { + if (progressText) progressText.textContent = _('updates.restarting'); + Notifications.success(msg); + setTimeout(() => pollServerRestart(), 3000); + } else { + Notifications.success(_('updates.install_success')); + if (progressEl) setTimeout(() => { progressEl.style.display = 'none'; }, 5000); + if (installBtn) installBtn.disabled = false; + } } catch (error) { Notifications.error(error.message || _('updates.install_failed')); if (progressEl) progressEl.style.display = 'none'; @@ -1124,7 +1176,7 @@ try { const data = await Utils.api('/api/settings/updates/backups'); - const backups = data.backups || []; + const backups = Array.isArray(data) ? data : (data.backups || []); if (!backups.length) { listEl.innerHTML = `

${_('updates.no_backups')}

`; @@ -1133,11 +1185,12 @@ let html = '
'; for (const b of backups) { - const date = new Date(b.timestamp).toLocaleString(); + const date = b.timestamp ? new Date(b.timestamp).toLocaleString() : ''; + const sha = b.sha ? ` · ${Utils.escapeHtml(b.sha)}` : ''; html += `
${Utils.escapeHtml(b.name)} - ${date} · ${b.fileCount} ${_('updates.files')} + ${date}${sha} · ${b.fileCount || b.filesBackedUp || 0} ${_('updates.files')}