| .. | .. |
|---|
| 1 | 1 | 'use strict'; |
|---|
| 2 | | -const APP_VERSION = 'v14-20260222'; |
|---|
| 2 | +const APP_VERSION = 'v15-20260226'; |
|---|
| 3 | 3 | |
|---|
| 4 | 4 | // ============================================================ |
|---|
| 5 | 5 | // OPS Dashboard — Vanilla JS Application (v6) |
|---|
| .. | .. |
|---|
| 827 | 827 | const downloadBtn = (!b.hasLocal && b.hasOffsite) |
|---|
| 828 | 828 | ? `<button class="btn btn-ghost btn-xs" style="color:#34d399;border-color:rgba(52,211,153,0.25);" onclick="downloadOffsiteBackup('${esc(b.project)}','${esc(b.env)}','${esc(b.name)}')">Download</button>` |
|---|
| 829 | 829 | : ''; |
|---|
| 830 | + const saveBtn = b.hasLocal |
|---|
| 831 | + ? `<button class="btn btn-ghost btn-xs" style="color:#60a5fa;border-color:rgba(96,165,250,0.25);" onclick="downloadBackupFile('${esc(b.project)}','${esc(b.env)}','${esc(b.name)}')">Save</button>` |
|---|
| 832 | + : ''; |
|---|
| 830 | 833 | h += `<tr> |
|---|
| 831 | 834 | <td style="padding-left:0.75rem;"><input type="checkbox" class="backup-cb" value="${esc(b.name)}"${checked} onclick="toggleBackupSelect('${esc(b.name)}')" style="accent-color:#3b82f6;cursor:pointer;"></td> |
|---|
| 832 | 835 | <td>${locationBadge}</td> |
|---|
| .. | .. |
|---|
| 834 | 837 | <td>${esc(b.size_human || '\u2014')}</td> |
|---|
| 835 | 838 | <td style="white-space:nowrap;"> |
|---|
| 836 | 839 | <button class="btn btn-danger btn-xs" onclick="openRestoreModal('${esc(b.project)}','${esc(b.env)}','${restoreSource}','${esc(b.name)}',${b.hasLocal},${b.hasOffsite})">Restore</button> |
|---|
| 840 | + ${saveBtn} |
|---|
| 837 | 841 | ${uploadBtn} |
|---|
| 838 | 842 | ${downloadBtn} |
|---|
| 839 | 843 | ${deleteBtn} |
|---|
| .. | .. |
|---|
| 927 | 931 | cachedBackups = null; |
|---|
| 928 | 932 | toast(`Deleted ${ok}${fail > 0 ? ', ' + fail + ' failed' : ''}`, fail > 0 ? 'warning' : 'success'); |
|---|
| 929 | 933 | if (currentPage === 'backups') renderBackups(); |
|---|
| 934 | +} |
|---|
| 935 | + |
|---|
| 936 | +function downloadBackupFile(project, env, name) { |
|---|
| 937 | + window.open(`/api/backups/download/${encodeURIComponent(project)}/${encodeURIComponent(env)}/${encodeURIComponent(name)}?token=${encodeURIComponent(getToken())}`, '_blank'); |
|---|
| 930 | 938 | } |
|---|
| 931 | 939 | |
|---|
| 932 | 940 | async function uploadOffsiteBackup(project, env, name) { |
|---|
| .. | .. |
|---|
| 1702 | 1710 | ih += '<span style="font-size:0.75rem;color:#6b7280;margin-left:auto;">content flows up</span>'; |
|---|
| 1703 | 1711 | ih += '</label>'; |
|---|
| 1704 | 1712 | ih += '</div>'; |
|---|
| 1713 | + ih += '<label style="display:flex;align-items:center;gap:0.5rem;font-size:0.875rem;color:#d1d5db;cursor:pointer;margin-top:0.875rem;">'; |
|---|
| 1714 | + ih += '<input type="checkbox" id="ops-sync-skip-backup" style="width:1rem;height:1rem;accent-color:#f59e0b;">'; |
|---|
| 1715 | + ih += 'Skip safety backup <span style="font-size:0.75rem;color:#f59e0b;margin-left:0.25rem;">(faster, no pre-sync snapshot)</span>'; |
|---|
| 1716 | + ih += '</label>'; |
|---|
| 1705 | 1717 | |
|---|
| 1706 | 1718 | info.innerHTML = ih; |
|---|
| 1707 | 1719 | startBtn.className = 'btn btn-primary btn-sm'; |
|---|
| .. | .. |
|---|
| 1871 | 1883 | if (type === 'promote') { |
|---|
| 1872 | 1884 | url = '/api/promote/' + encodeURIComponent(project) + '/' + encodeURIComponent(fromEnv) + '/' + encodeURIComponent(toEnv) + '?dry_run=' + dryRun + '&token=' + encodeURIComponent(getToken()); |
|---|
| 1873 | 1885 | } else if (type === 'sync') { |
|---|
| 1874 | | - url = '/api/sync/' + encodeURIComponent(project) + '?from=' + encodeURIComponent(fromEnv) + '&to=' + encodeURIComponent(toEnv) + '&dry_run=' + dryRun + '&token=' + encodeURIComponent(getToken()); |
|---|
| 1886 | + const skipBackupEl = document.getElementById('ops-sync-skip-backup'); |
|---|
| 1887 | + const skipBackup = skipBackupEl ? skipBackupEl.checked : false; |
|---|
| 1888 | + url = '/api/sync/' + encodeURIComponent(project) + '?from=' + encodeURIComponent(fromEnv) + '&to=' + encodeURIComponent(toEnv) + '&dry_run=' + dryRun + (skipBackup ? '&skip_backup=true' : '') + '&token=' + encodeURIComponent(getToken()); |
|---|
| 1875 | 1889 | } else if (type === 'restart' || type === 'rebuild') { |
|---|
| 1876 | 1890 | url = '/api/rebuild/' + encodeURIComponent(project) + '/' + encodeURIComponent(fromEnv) |
|---|
| 1877 | 1891 | + '?action=' + encodeURIComponent(type) + '&token=' + encodeURIComponent(getToken()); |
|---|