ux: format activity feed JSON, remove default creds hint, add login loading state

This commit is contained in:
Leo
2026-05-27 18:35:52 -04:00
parent 510bafc919
commit 6bf8a3e970
+59 -3
View File
@@ -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;
}
</style>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://cdn.jsdelivr.net/npm/exifr@7/dist/lite.umd.js"></script>
@@ -731,7 +747,6 @@
<input id="loginUser" class="input-field" type="text" placeholder="Username" autocomplete="username">
<input id="loginPass" class="input-field" type="password" placeholder="Password" autocomplete="current-password">
<button id="loginBtn" class="btn btn-primary btn-block" onclick="doLogin()">Sign In</button>
<div style="text-align:center;font-size:12px;color:var(--text3);margin-top:10px;">Default: admin / changeme</div>
</div>
</div>
@@ -779,6 +794,9 @@
<button class="nav-item" data-page="exifscanner" onclick="switchPage('exifscanner')">
<span class="ni-icon">📸</span><span>EXIF Scanner</span>
</button>
<button class="nav-item" data-page="route" onclick="switchPage('route')">
<span class="ni-icon">🗺️</span><span>Route</span>
</button>
</div>
<div class="sidebar-footer">
<button id="logoutBtn" class="logout-btn" onclick="doLogout()">🚪 Sign out</button>
@@ -1116,6 +1134,11 @@
</div>
<div id="exifPrevResults"></div>
</div>
</div> <!-- /pageExifscanner -->
<!-- ── ROUTE PLANNER (iframe) ── -->
<div id="pageRoute" class="page hidden">
<iframe id="routeFrame" src="https://route.ourpad.casa" frameborder="0"></iframe>
</div>
</div>
@@ -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 `<div class="activity-item">
<div class="act-icon ${icon}">${getActEmoji(a.action)}</div>
<div class="act-info">
<div class="act-text">${esc(a.details || a.action || '')} <span class="act-user">${esc(a.user_name || '')}</span></div>
<div class="act-text">${esc(details)} <span class="act-user">${esc(a.user_name || '')}</span></div>
<div class="act-time">${timeAgo(a.created_at)}</div>
</div>
</div>`;
@@ -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';
}
</script>
</body>
</html>