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';
+}