diff --git a/audit/audit.log b/audit/audit.log index 592ec12..30ab031 100644 --- a/audit/audit.log +++ b/audit/audit.log @@ -1,2 +1,3 @@ {"action": "skill_run", "skill": "heartbeat", "agent": "opencode", "run_id": "d52d517a", "timestamp": "2026-05-17T09:58:07.559900+00:00", "id": "36284cbc"} {"action": "skill_run", "skill": "devops-audit", "agent": "opencode", "run_id": "92cd4f97", "timestamp": "2026-05-17T09:59:08.213032+00:00", "id": "40695cd7"} +{"action": "skill_run", "skill": "heartbeat", "agent": "opencode", "run_id": "f18c903b", "timestamp": "2026-05-17T10:18:44.356138+00:00", "id": "c9117c6d"} diff --git a/dashboard/api.js b/dashboard/api.js index 7b262fa..f639be8 100644 --- a/dashboard/api.js +++ b/dashboard/api.js @@ -1,48 +1,35 @@ const api = { async get(path) { const r = await fetch(path); - if (!r.ok) throw new Error(`GET ${path} failed: ${r.status}`); + if (!r.ok) { const e = await r.json().catch(() => ({})); throw new Error(e.detail || `Request failed: ${r.status}`); } return r.json(); }, - async post(path, body = {}) { - const r = await fetch(path, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - }); - if (!r.ok) throw new Error(`POST ${path} failed: ${r.status}`); + const r = await fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); + if (!r.ok) { const e = await r.json().catch(() => ({})); throw new Error(e.detail || `Request failed: ${r.status}`); } return r.json(); }, - async put(path, body = {}) { - const r = await fetch(path, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - }); - if (!r.ok) throw new Error(`PUT ${path} failed: ${r.status}`); + const r = await fetch(path, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); + if (!r.ok) { const e = await r.json().catch(() => ({})); throw new Error(e.detail || `Request failed: ${r.status}`); } return r.json(); }, - async del(path) { const r = await fetch(path, { method: 'DELETE' }); - if (!r.ok) throw new Error(`DELETE ${path} failed: ${r.status}`); + if (!r.ok) { const e = await r.json().catch(() => ({})); throw new Error(e.detail || `Request failed: ${r.status}`); } return r.json(); }, - - // Convenience methods getStatus: () => api.get('/api/status'), getBrain: () => api.get('/api/brain'), - getBrainFile: (name) => api.get(`/api/brain/${name}`), - updateBrainFile: (name, content) => api.put(`/api/brain/${name}`, { content }), + getBrainFile: (name) => api.get(`/api/brain/${encodeURIComponent(name)}`), + updateBrainFile: (name, content) => api.put(`/api/brain/${encodeURIComponent(name)}`, { content }), getSkills: () => api.get('/api/skills'), - getSkill: (name) => api.get(`/api/skills/${name}`), - runSkill: (name, input = '', agent = 'auto') => api.post(`/api/skills/${name}/run`, { input, agent }), - getSkillEval: (name) => api.get(`/api/skills/${name}/eval`), + getSkill: (name) => api.get(`/api/skills/${encodeURIComponent(name)}`), + runSkill: (name, input = '', agent = 'auto') => api.post(`/api/skills/${encodeURIComponent(name)}/run`, { input, agent }), + getSkillEval: (name) => api.get(`/api/skills/${encodeURIComponent(name)}/eval`), getJobs: () => api.get('/api/scheduler/jobs'), createJob: (job) => api.post('/api/scheduler/jobs', job), - deleteJob: (id) => api.del(`/api/scheduler/jobs/${id}`), + deleteJob: (id) => api.del(`/api/scheduler/jobs/${encodeURIComponent(id)}`), getAudit: (limit = 100) => api.get(`/api/audit?limit=${limit}`), getCost: () => api.get('/api/cost'), recordCost: (data) => api.post('/api/cost/record', data), diff --git a/dashboard/app.js b/dashboard/app.js index 5460ed3..bff48f1 100644 --- a/dashboard/app.js +++ b/dashboard/app.js @@ -1,11 +1,22 @@ const pageCache = {}; +async function loadPage(name) { + if (pageCache[name]) return pageCache[name]; + try { + await loadScript(`pages/${name}.js`); + pageCache[name] = true; + } catch (err) { + console.error(`Failed to load page: ${name}`, err); + throw err; + } +} + function loadScript(src) { return new Promise((resolve, reject) => { - if (pageCache[src]) { resolve(pageCache[src]); return; } + if (document.querySelector(`script[src="${src}"]`)) { resolve(); return; } const script = document.createElement('script'); script.src = src; - script.onload = () => { pageCache[src] = true; resolve(); }; + script.onload = () => resolve(); script.onerror = () => reject(new Error(`Failed to load ${src}`)); document.body.appendChild(script); }); @@ -13,104 +24,53 @@ function loadScript(src) { async function navigate(page) { const hash = page || window.location.hash.slice(1) || 'dashboard'; - const content = document.getElementById('pageContent'); + if (!hash) { window.location.hash = 'dashboard'; return; } document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active')); const navItem = document.querySelector(`[data-page="${hash}"]`); if (navItem) navItem.classList.add('active'); - content.innerHTML = '
Loading...
'; + 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 = `
Loading ${info.title}...
`; try { - switch (hash) { - case 'dashboard': - content.innerHTML = '
Loading dashboard...
'; - await loadScript('pages/dashboard.js'); - content.innerHTML = ''; - renderDashboard(); - break; - case 'skills': - content.innerHTML = '
Loading skills...
'; - await loadScript('pages/skills.js'); - content.innerHTML = ''; - renderSkills(); - break; - case 'memory': - content.innerHTML = '
Loading memory...
'; - await loadScript('pages/memory.js'); - content.innerHTML = ''; - renderMemory(); - break; - case 'scheduler': - content.innerHTML = '
Loading scheduler...
'; - await loadScript('pages/scheduler.js'); - content.innerHTML = ''; - renderScheduler(); - break; - case 'audit': - content.innerHTML = '
Loading audit...
'; - await loadScript('pages/audit.js'); - content.innerHTML = ''; - renderAudit(); - break; - case 'cost': - content.innerHTML = '
Loading cost analytics...
'; - await loadScript('pages/cost-analytics.js'); - content.innerHTML = ''; - renderCostAnalytics(); - break; - case 'plugins': - content.innerHTML = '
Loading plugins...
'; - await loadScript('pages/plugins.js'); - content.innerHTML = ''; - renderPlugins(); - break; - case 'backups': - content.innerHTML = '
Loading backups...
'; - await loadScript('pages/backups.js'); - content.innerHTML = ''; - renderBackups(); - break; - case 'prompts': - content.innerHTML = '
Loading prompts...
'; - await loadScript('pages/prompts.js'); - content.innerHTML = ''; - renderPrompts(); - break; - case 'standards': - content.innerHTML = '
Loading standards...
'; - await loadScript('pages/standards.js'); - content.innerHTML = ''; - renderStandards(); - break; - case 'settings': - content.innerHTML = '
Loading settings...
'; - await loadScript('pages/settings.js'); - content.innerHTML = ''; - renderSettings(); - break; - default: - content.innerHTML = '
๐Ÿ”

Page not found

'; + 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'; + await renderFn(); + } else { + content.innerHTML = `
๐Ÿ”
Page not found
The page "${hash}" doesn't have a render function
`; } } catch (err) { - content.innerHTML = `
โš 

Error loading page

${escapeHtml(err.message)}

`; - console.error(err); + content.innerHTML = `
โš 
Failed to load
${escapeHtml(err.message)}
`; } } +function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } + async function updateAgentStatus() { try { const status = await api.getStatus(); - const el = document.getElementById('agentStatus'); const agents = status.agents || []; - const allOnline = agents.every(a => a.status === 'online'); - const anyOnline = agents.some(a => a.status === 'online'); - el.innerHTML = ` -
- ${agents.filter(a => a.status === 'online').length}/${agents.length} agents online - `; + 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 { - document.getElementById('agentStatus').innerHTML = '
Offline'; + const bar = document.getElementById('agentStatusBar'); + if (bar) { bar.querySelector('.agent-dot').className = 'agent-dot offline'; bar.querySelector('span').textContent = 'Disconnected'; } } } @@ -119,5 +79,5 @@ window.addEventListener('DOMContentLoaded', () => { loadTheme(); navigate(window.location.hash.slice(1) || 'dashboard'); updateAgentStatus(); - setInterval(updateAgentStatus, 30000); + setInterval(updateAgentStatus, 15000); }); diff --git a/dashboard/index.html b/dashboard/index.html index 50a80a2..40c27b7 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -4,74 +4,70 @@ Agentic OS + + - + -
-
-
Loading Agentic OS...
-
-
+
+
+
+ Dashboard + Overview +
+
+ +
+
+
+
Loading Agentic OS...
+
+
-
+
+
- - - + + + diff --git a/dashboard/pages/audit.js b/dashboard/pages/audit.js index 170677a..01170e9 100644 --- a/dashboard/pages/audit.js +++ b/dashboard/pages/audit.js @@ -2,62 +2,84 @@ async function renderAudit() { const content = document.getElementById('pageContent'); content.innerHTML = `