diff --git a/static/index.html b/static/index.html index 4c0ce5a..731c801 100644 --- a/static/index.html +++ b/static/index.html @@ -136,6 +136,7 @@ flex: 1; padding: 24px; max-width: calc(100vw - var(--sidebar-w)); + position: relative; } .page-title { font-size: 22px; @@ -716,6 +717,21 @@ .cs-repl-status.approved { background: rgba(72,199,142,0.15); color: var(--green); } .cs-repl-status.rejected { background: rgba(240,61,62,0.15); color: var(--red); } .cs-repl-resolve-all { margin-top: 8px; } + + /* ── Route Planner iframe ── */ + #pageRoute { + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + margin-top: 0; padding: 0; + display: flex; flex-direction: column; + } + #pageRoute.hidden { display: none !important; } + #routeFrame { + width: 100%; + flex: 1; + border: none; + background: #fff; + } @@ -731,7 +747,6 @@ -
Default: admin / changeme
@@ -779,6 +794,9 @@ +
+ + + + @@ -1201,7 +1224,7 @@ function showToast(msg, isError = false) { t.textContent = msg; t.className = 'toast' + (isError ? ' error' : ''); t.classList.add('show'); - setTimeout(() => t.classList.remove('show'), 3000); + setTimeout(() => t.classList.remove('show'), 4000); } let modalResolve = null; @@ -1245,6 +1268,7 @@ function switchPage(page) { else if (page === 'activity') loadActivity(); else if (page === 'cantaloupe') loadCantaloupeSync(); else if (page === 'exifscanner') loadExifScanner(); + else if (page === 'route') initRoutePage(); } function closeDetail() { if (AppState._prevPage) switchPage(AppState._prevPage); @@ -1257,10 +1281,13 @@ function closeDetail() { async function doLogin() { const username = document.getElementById('loginUser').value.trim(); const password = document.getElementById('loginPass').value; + const loginBtn = document.getElementById('loginBtn'); if (!username || !password) { showLoginError('Please enter username and password'); return; } + loginBtn.disabled = true; + loginBtn.textContent = 'Signing in...'; try { const data = await api('/api/auth/login', { method: 'POST', @@ -1276,6 +1303,9 @@ async function doLogin() { switchPage('dashboard'); } catch (e) { showLoginError(e.message); + } finally { + loginBtn.disabled = false; + loginBtn.textContent = 'Sign In'; } } @@ -1400,10 +1430,23 @@ function renderDashActivity(items) { } el.innerHTML = items.map(a => { const icon = getActIcon(a.action); + let details = a.details || a.action || ''; + // Try to pretty-print JSON details + try { + const parsed = JSON.parse(details); + if (typeof parsed === 'object' && parsed !== null) { + const parts = []; + if (parsed.assets_updated !== undefined) parts.push(`${parsed.assets_updated} asset(s) updated`); + if (parsed.fields_applied !== undefined) parts.push(`${parsed.fields_applied} field(s) applied`); + if (parsed.assets_created !== undefined) parts.push(`${parsed.assets_created} created`); + if (parts.length > 0) details = parts.join(', '); + else details = JSON.stringify(parsed).replace(/[{}"]/g, '').replace(/,/g, ', '); + } + } catch(e) { /* not JSON, show as-is */ } return `
${getActEmoji(a.action)}
-
${esc(a.details || a.action || '')} ${esc(a.user_name || '')}
+
${esc(details)} ${esc(a.user_name || '')}
${timeAgo(a.created_at)}
`; @@ -4022,6 +4065,19 @@ function openExifLightbox(src) { function closeExifLightbox() { if (exifLightboxEl) exifLightboxEl.style.display = 'none'; } + +// ═════════════════════════════════════════════════════════════════════════════ +// ROUTE PLANNER +// ═════════════════════════════════════════════════════════════════════════════ +function initRoutePage() { + const iframe = document.getElementById('routeFrame'); + if (!iframe) return; + // Force-reload on each visit so the route data is always fresh + if (iframe.dataset.loaded) { + iframe.src = iframe.src; + } + iframe.dataset.loaded = '1'; +}