diff --git a/web/static/index.html b/web/static/index.html index 3c925ed..107169d 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -585,6 +585,54 @@ + +
+

📤 Push to Main App

+ +
+ Sync seed data (seed-data/assets.db) into the canteen asset tracker main app DB + with schema mapping (customers/locations/assets). +
+ + +
+
+
Target
+
+
+
🛠️ Dev
+
Loading...
+
+
+
🏭 Prod
+
Loading...
+
+
+
+
+ + +
+ + Dry run — preview changes without writing +
+ + +
+ + + +
+ + + +
+

⚠️ Database Management

@@ -1076,9 +1124,119 @@ statusEl.innerHTML = `🗑️ Deleted ${d.records_deleted} records. Database is now empty.`; refreshDashboard(); } catch (e) { - statusEl.innerHTML = `❌ ${esc(e.message)}`; + statusEl.innerHTML = `❌ ${esc(e.message)}`; } } + + // ─── Sync / Push to Main App ────────────────────────────────────── + + let syncTarget = 'dev'; + + async function refreshSyncTargets() { + const d = await fetch('/api/sync/targets').then(r => r.json()); + const src = d.source || {}; + document.getElementById('syncSourcePath').textContent = src.path || 'seed-data/assets.db'; + + for (const [name, info] of Object.entries(d.targets || {})) { + const descEl = document.getElementById('syncTarget' + name.charAt(0).toUpperCase() + name.slice(1) + 'Desc'); + if (!descEl) continue; + if (info.exists) { + descEl.innerHTML = `✓ ${info.path.replace(/^.*\\//, '')}`; + } else { + descEl.innerHTML = `✗ Not found`; + } + } + } + + function selectSyncTarget(target) { + syncTarget = target; + document.getElementById('syncTargetDev').classList.toggle('selected', target === 'dev'); + document.getElementById('syncTargetProd').classList.toggle('selected', target === 'prod'); + } + + async function runSync() { + const btn = document.getElementById('btnSync'); + const status = document.getElementById('syncStatus'); + const result = document.getElementById('syncResult'); + + btn.disabled = true; + btn.innerHTML = ' Pushing...'; + status.textContent = ''; + result.style.display = 'none'; + document.getElementById('badgeStatus').textContent = 'Syncing...'; + + const dryRun = document.getElementById('toggleDryRun').checked; + const params = new URLSearchParams({ target: syncTarget, dry_run: dryRun }); + + try { + const res = await fetch('/api/sync?' + params, { method: 'POST' }); + if (!res.ok) { + const err = await res.json(); + throw new Error(err.detail || 'Sync failed'); + } + const d = await res.json(); + showSyncResult(d); + document.getElementById('badgeStatus').textContent = dryRun ? 'Dry run ✓' : 'Synced ✓'; + refreshDashboard(); + } catch (e) { + status.innerHTML = `❌ ${esc(e.message)}`; + document.getElementById('badgeStatus').textContent = 'Error'; + } finally { + btn.disabled = false; + btn.innerHTML = '📤 Push to Main App'; + } + } + + function showSyncResult(d) { + const el = document.getElementById('syncResult'); + const isDry = d.dry_run; + + let html = `
`; + html += isDry ? '🔍 Dry run — no changes written' : '✅ Sync complete!'; + html += '
'; + + html += '
'; + html += `
${fmt(d.assets_created)}
Assets Created
`; + html += `
${fmt(d.assets_updated)}
Assets Updated
`; + html += `
${fmt(d.assets_skipped || 0)}
Skipped
`; + html += `
${fmt(d.customers_created)}
Customers Created
`; + html += `
${fmt(d.locations_created)}
Locations Created
`; + html += `
${fmt(d.total_actions)}
Total Actions
`; + html += '
'; + + // Source/target info + html += `
`; + html += `Source: ${esc(d.source)}
`; + html += `Target: ${esc(d.target)}`; + if (d.target_before) { + const b = d.target_before; + html += `
Target had: ${b.customers} customers, ${b.locations} locations, ${b.assets} assets`; + } + html += '
'; + + // Sample actions + if (d.first_10?.length) { + html += '
Sample actions:
'; + html += '
'; + html += d.first_10.map(a => esc(a)).join('
'); + html += '
'; + } + + // Errors + if (d.errors?.length) { + html += `
`; + html += `Errors (${d.errors.length}):
`; + html += d.errors.map(e => esc(e)).join('
'); + html += '
'; + } + + el.innerHTML = html; + el.style.display = ''; + el.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } + + // Init sync targets on load + setTimeout(refreshSyncTargets, 100);