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 = '
${escapeHtml(err.message)}