const API = '';
let currentPage = 'dashboard';
async function api(path, opts = {}) {
const res = await fetch(API + path, {
headers: { 'Content-Type': 'application/json' },
...opts
});
return res.json();
}
function toast(msg) {
let t = document.querySelector('.toast');
if (!t) { t = document.createElement('div'); t.className = 'toast'; document.body.appendChild(t); }
t.textContent = msg;
t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 3000);
}
function navigate(page) {
currentPage = page;
document.querySelectorAll('#nav-links a').forEach(a => {
a.classList.toggle('active', a.dataset.page === page);
});
renderPage(page);
}
async function renderPage(page) {
const el = document.getElementById('content');
switch (page) {
case 'dashboard': return renderDashboard(el);
case 'albums': return renderAlbums(el);
case 'slideshow': return renderSlideshow(el);
case 'display': return renderDisplay(el);
case 'device': return renderDevice(el);
case 'firmware': return renderFirmware(el);
}
}
async function renderDashboard(el) {
const status = await api('/api/status');
el.innerHTML = `
Status
Battery${status.battery}%${status.charging ? ' ⚡' : ''}
WiFi Signal${status.wifi_rssi} dBm
IP Address${status.ip}
Uptime${Math.floor(status.uptime/60)}m
Free RAM${Math.floor(status.free_heap/1024)}KB
Free PSRAM${Math.floor(status.free_psram/1024)}KB
Controls
`;
}
async function renderAlbums(el) {
const [albums, settings] = await Promise.all([api('/api/albums'), api('/api/settings')]);
const selected = JSON.parse(settings.albums_json || '[]');
el.innerHTML = `
`;
document.getElementById('saveAlbums').onclick = async () => {
const ids = [...el.querySelectorAll('input[type=checkbox]:checked')].map(c => c.value);
await api('/api/albums/select', { method: 'POST', body: JSON.stringify({ album_ids: ids }) });
toast('Albums saved');
};
}
async function renderSlideshow(el) {
const settings = await api('/api/settings');
const intervals = [1, 5, 15, 30, 60];
const modes = ['Random', 'Chronological', 'Reverse Chrono', 'Favorites Weighted'];
el.innerHTML = `
Interval
${intervals.map(i => ``).join('')}
Cycling Mode
`;
el.querySelectorAll('[data-interval]').forEach(btn => {
btn.onclick = () => {
el.querySelectorAll('[data-interval]').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
};
});
document.getElementById('saveSlideshow').onclick = async () => {
const interval = parseInt(el.querySelector('[data-interval].active')?.dataset.interval || '5');
const mode = parseInt(document.getElementById('cycleMode').value);
await api('/api/settings', { method: 'POST', body: JSON.stringify({ interval_min: interval, cycle_mode: mode }) });
toast('Slideshow settings saved');
};
}
async function renderDisplay(el) {
const settings = await api('/api/settings');
const flags = settings.meta_flags;
el.innerHTML = `
Image Quality
Metadata Overlay
`;
document.getElementById('saveDisplay').onclick = async () => {
let flags = 0;
el.querySelectorAll('[data-flag]').forEach(cb => { if (cb.checked) flags |= parseInt(cb.dataset.flag); });
await api('/api/settings', { method: 'POST', body: JSON.stringify({
img_quality: parseInt(document.getElementById('imgQuality').value),
meta_flags: flags,
meta_pos: parseInt(document.getElementById('metaPos').value)
})});
toast('Display settings saved');
};
}
async function renderDevice(el) {
const settings = await api('/api/settings');
el.innerHTML = `
Device Actions
`;
document.getElementById('saveImmich').onclick = async () => {
const url = document.getElementById('immichUrl').value.trim();
const key = document.getElementById('immichKey').value.trim();
if (!url || !key) return toast('URL and API key are required');
await api('/api/settings', { method: 'POST', body: JSON.stringify({ immich_url: url, immich_key: key }) });
toast('Immich settings saved. Reboot to apply.');
};
document.getElementById('testConn').onclick = async () => {
const status = document.getElementById('connStatus');
status.textContent = 'Testing...';
status.style.color = '';
try {
const result = await api('/api/test-connection', { method: 'POST' });
status.textContent = result.msg;
status.style.color = result.ok ? '#2e7d32' : '#c62828';
} catch (e) {
status.textContent = 'Request failed: ' + e.message;
status.style.color = '#c62828';
}
};
}
async function renderFirmware(el) {
el.innerHTML = `
`;
document.getElementById('uploadFw').onclick = async () => {
const file = document.getElementById('fwFile').files[0];
if (!file) return toast('Select a file first');
const formData = new FormData();
formData.append('firmware', file);
document.getElementById('fwStatus').textContent = 'Uploading...';
const res = await fetch('/api/firmware', { method: 'POST', body: formData });
const data = await res.json();
document.getElementById('fwStatus').textContent = data.msg || (data.ok ? 'Success!' : 'Failed');
};
}
// Navigation
document.querySelectorAll('#nav-links a').forEach(a => {
a.addEventListener('click', (e) => { e.preventDefault(); navigate(a.dataset.page); });
});
// Initial render
navigate('dashboard');