/* @jsxRuntime classic */ /* @jsx React.createElement */ // AssemblyWright — Backup / Restore (snapshots) surface // // Self-contained: loaded by index.html, exposes window.SnapshotsPage, rendered // by app.jsx's page switch with props { api }. // Named restore points for the Knowledge Base + Skill Mappings state — take a // checkpoint before a risky import or bulk edit and roll back with one click. // Restore requires typing "restore" to confirm (and Admin PIN if configured). // ───────────────────────────────────────────────────────────────────────────── (function () { const { useState, useEffect } = React; function SnapshotsPage({ api }) { const [snaps, setSnaps] = useState(null); const [label, setLabel] = useState(''); const [pin, setPin] = useState(''); const [busy, setBusy] = useState(false); const [confirmId, setConfirmId] = useState(null); const [confirmText, setConfirmText] = useState(''); const [msg, setMsg] = useState(null); async function load() { try { setSnaps(await api('GET', '/api/snapshots')); } catch (e) { setSnaps([]); } } useEffect(() => { load(); }, []); async function checkpoint() { setBusy(true); setMsg(null); try { await api('POST', '/api/snapshots', { label: label.trim() || 'Checkpoint' }); setLabel(''); await load(); setMsg({ ok: true, text: 'Checkpoint saved.' }); } catch (e) { setMsg({ ok: false, text: 'Could not save checkpoint.' }); } finally { setBusy(false); } } async function restore(id) { setBusy(true); setMsg(null); try { const headers = { 'Content-Type': 'application/json' }; if (pin) headers['X-Admin-Pin'] = pin; const res = await fetch('/api/snapshots/' + id + '/restore', { method: 'POST', headers }); if (!res.ok) { throw new Error(res.status + ''); } setConfirmId(null); setConfirmText(''); await load(); setMsg({ ok: true, text: 'Restored. A safety checkpoint of the previous state was saved automatically.' }); } catch (e) { const isPin = ('' + e).indexOf('403') >= 0; setMsg({ ok: false, text: isPin ? 'Restore needs the Admin PIN entered above.' : 'Restore failed.' }); } finally { setBusy(false); } } const countSummary = (counts) => Object.entries(counts || {}) .filter(([, v]) => v > 0) .map(([k, v]) => v + ' ' + k.replace(/_/g, ' ') .replace('org context entries', 'KB notes') .replace('skill knowledge maps', 'skill maps')) .join(' · ') || 'empty'; return React.createElement('div', { className: 'page' }, React.createElement('div', { style: { marginBottom: 12 } }, React.createElement('div', { style: { fontWeight: 600, fontSize: '0.95rem' } }, 'Backup / Restore'), React.createElement('div', { className: 'text-muted', style: { fontSize: '0.82rem', marginTop: 2 } }, 'Named restore points for your Knowledge Base and Skill Mappings. A checkpoint is saved automatically before every Salesforce import. This is separate from the whole-database backup (scripts/backup_pg.sh).') ), msg && React.createElement('div', { style: { fontSize: '0.82rem', marginBottom: 12, padding: '8px 12px', borderRadius: 8, border: '1px solid ' + (msg.ok ? '#1f5132' : '#5b2330'), background: msg.ok ? '#0e1f16' : '#1f1014', color: msg.ok ? '#4ade80' : '#f08080' } }, msg.text), React.createElement('div', { className: 'card', style: { maxWidth: 720, marginBottom: 14 } }, React.createElement('div', { className: 'row', style: { gap: 8, flexWrap: 'wrap' } }, React.createElement('input', { placeholder: 'Checkpoint name (optional)', value: label, onChange: e => setLabel(e.target.value), style: { flex: 1, minWidth: 180, fontSize: '0.82rem' } }), React.createElement('button', { className: 'btn btn-gold btn-sm', disabled: busy, onClick: checkpoint }, busy ? 'Saving…' : '+ Checkpoint now'), React.createElement('input', { type: 'password', placeholder: 'Admin PIN (required for Restore)', value: pin, onChange: e => setPin(e.target.value), style: { width: 200, fontSize: '0.82rem' } }) ) ), snaps === null && React.createElement('div', { className: 'text-muted' }, 'Loading…'), snaps && snaps.length === 0 && React.createElement('div', { className: 'empty-state', style: { paddingTop: 24 } }, 'No snapshots yet. Take a checkpoint before a risky change.'), snaps && snaps.map(s => React.createElement('div', { key: s.id, className: 'card', style: { maxWidth: 720, marginBottom: 8, padding: '10px 14px' } }, React.createElement('div', { className: 'row', style: { justifyContent: 'space-between', alignItems: 'center', gap: 8 } }, React.createElement('div', null, React.createElement('div', { style: { fontSize: '0.86rem', color: '#e8e6df' } }, s.label, s.kind === 'auto' && React.createElement('span', { className: 'badge badge-gray', style: { marginLeft: 6 } }, 'auto')), React.createElement('div', { className: 'text-muted', style: { fontSize: '0.74rem', marginTop: 2 } }, (s.createdAt || '').slice(0, 19).replace('T', ' ') + ' UTC · ' + countSummary(s.counts)) ), confirmId === s.id ? React.createElement('div', { className: 'row', style: { gap: 6, alignItems: 'center' } }, React.createElement('input', { placeholder: 'type "restore"', value: confirmText, autoFocus: true, onChange: e => setConfirmText(e.target.value), style: { width: 120, fontSize: '0.78rem' } }), React.createElement('button', { className: 'btn btn-sm btn-gold', disabled: busy || confirmText.trim().toLowerCase() !== 'restore', onClick: () => restore(s.id) }, 'Confirm'), React.createElement('button', { className: 'btn btn-ghost btn-sm', onClick: () => { setConfirmId(null); setConfirmText(''); } }, 'Cancel')) : React.createElement('button', { className: 'btn btn-outline btn-sm', disabled: busy, onClick: () => { setConfirmId(s.id); setConfirmText(''); setMsg(null); } }, '↺ Restore') ) )) ); } window.SnapshotsPage = SnapshotsPage; })();