const pageCache = {};
const APP_VERSION = '0.4.1';
const PAGE_BASE = '/dashboard/pages/';
async function loadPage(name) {
if (pageCache[name]) return pageCache[name];
try {
await loadScript(`${PAGE_BASE}${name}.js`);
pageCache[name] = true;
} catch (err) {
showToast(`Failed to load page: ${name}`, 'error');
throw err;
}
}
function loadScript(src) {
return new Promise((resolve, reject) => {
const versioned = `${src}?v=${APP_VERSION}`;
if (document.querySelector(`script[src="${versioned}"]`)) { resolve(); return; }
const script = document.createElement('script');
script.src = versioned;
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Failed to load ${src}`));
document.body.appendChild(script);
});
}
async function navigate(page) {
const hash = page || window.location.hash.slice(1) || 'dashboard';
if (!hash) { window.location.hash = 'dashboard'; return; }
// Show loading bar
const bar = document.getElementById('topLoadingBar');
if (bar) { bar.classList.add('active'); bar.style.width = '30%'; }
document.querySelectorAll('.nav-item, .bottom-nav-item').forEach(el => el.classList.remove('active'));
const navItem = document.querySelector(`[data-page="${hash}"]`);
if (navItem) navItem.classList.add('active');
const info = PAGE_TITLES[hash] || { title: 'Unknown', breadcrumb: '' };
document.getElementById('pageTitle').textContent = info.title;
document.getElementById('pageBreadcrumb').textContent = info.breadcrumb;
const content = document.getElementById('pageContent');
content.innerHTML = `
`;
try {
await loadPage(hash);
const renderFn = window[`render${capitalize(hash.replace(/-./g, m => m[1].toUpperCase()))}`];
if (renderFn) {
content.innerHTML = '';
content.className = 'page-content page-enter';
if (bar) bar.style.width = '70%';
await renderFn();
if (bar) { bar.style.width = '100%'; setTimeout(() => { bar.style.width = '0'; bar.classList.remove('active'); }, 400); }
} else {
content.innerHTML = `🔍
Page not found
The page "${hash}" doesn't have a render function
`;
if (bar) { bar.style.width = '0'; bar.classList.remove('active'); }
}
} catch (err) {
content.innerHTML = `⚠
Failed to load
${escapeHtml(err.message)}
`;
if (bar) { bar.style.width = '0'; bar.classList.remove('active'); }
}
}
function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); }
async function updateAgentStatus() {
try {
const status = await api.getStatus();
const agents = status.agents || [];
const bar = document.getElementById('agentStatusBar');
const online = agents.filter(a => a.status === 'online').length;
const total = agents.length;
const dot = bar.querySelector('.agent-dot');
if (online === total) { dot.className = 'agent-dot online'; bar.querySelector('span').textContent = 'All agents online'; }
else if (online > 0) { dot.className = 'agent-dot warning'; bar.querySelector('span').textContent = `${online}/${total} online`; }
else { dot.className = 'agent-dot offline'; bar.querySelector('span').textContent = 'All agents offline'; }
const badge = document.getElementById('skillCount');
if (badge && status.skills_count !== undefined) badge.textContent = status.skills_count;
} catch {
const bar = document.getElementById('agentStatusBar');
if (bar) { bar.querySelector('.agent-dot').className = 'agent-dot offline'; bar.querySelector('span').textContent = 'Disconnected'; }
}
}
window.addEventListener('hashchange', () => navigate());
window.addEventListener('DOMContentLoaded', () => {
loadTheme();
navigate(window.location.hash.slice(1) || 'dashboard');
updateAgentStatus();
setInterval(updateAgentStatus, 15000);
});