Mounts agents router and schema init, adds VM123 checks, chat copilot, Desk UI module, isolated docker-compose staging on ports 8180/8192, and full spec documentation without touching production ports. Co-authored-by: Cursor <cursoragent@cursor.com>
91 lines
4.9 KiB
JavaScript
91 lines
4.9 KiB
JavaScript
(function () {
|
|
const esc = (s) => String(s ?? '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
|
|
async function api(path, opts = {}) {
|
|
const h = { ...(opts.headers || {}), 'Content-Type': 'application/json' };
|
|
const t = window.DeskAuth?.getToken?.();
|
|
if (t) h.Authorization = `Bearer ${t}`;
|
|
const r = await fetch(`/api/v1/agents${path}`, { ...opts, headers: h });
|
|
if (!r.ok) {
|
|
const err = await r.text();
|
|
throw new Error(`${r.status} ${err.slice(0, 200)}`);
|
|
}
|
|
return r.json();
|
|
}
|
|
|
|
async function sendChat(question) {
|
|
return api('/chat', { method: 'POST', body: JSON.stringify({ question, include_findings: true }) });
|
|
}
|
|
|
|
async function renderAgenticOps() {
|
|
const el = document.getElementById('agentic-ops-content');
|
|
if (!el) return;
|
|
el.innerHTML = '<p class="loading">Carregando Agentic Ops…</p>';
|
|
try {
|
|
const [health, scenarios, findings, log] = await Promise.all([
|
|
api('/health'), api('/scenarios'), api('/findings?limit=30'), api('/action-log?limit=40'),
|
|
]);
|
|
const tier = health.tier === 't1' ? 'T1 LLM' : 'T0';
|
|
const ollama = health.ollama
|
|
? `<span class="pill pill-ok">Ollama OK · ${esc(health.model || '')}</span>`
|
|
: '<span class="pill pill-warn">Ollama offline — modo T0</span>';
|
|
const sRows = (scenarios.scenarios || []).map(s =>
|
|
`<tr><td><code>${esc(s.id)}</code></td><td>${esc(s.title)}</td><td>${esc(s.last_run_status||'—')}</td><td class="ticket-meta">${esc(s.last_run_at||'—')}</td></tr>`
|
|
).join('');
|
|
const fRows = (findings.findings || []).map(f =>
|
|
`<article class="card agentic-finding"><h3>${esc(f.title)} <span class="pill">${esc(f.severity)}</span></h3>`
|
|
+ `<p class="ticket-meta">${esc(f.created_at)}</p>`
|
|
+ (f.suggested_human_action ? `<p><strong>Acção:</strong> ${esc(f.suggested_human_action)}</p>` : '')
|
|
+ `<button type="button" class="btn btn-ghost btn-sm" data-ack="${f.id}">Marcar visto</button></article>`
|
|
).join('') || '<p class="empty">Sem findings abertos.</p>';
|
|
const lRows = (log.events || []).map(e =>
|
|
`<tr><td class="ticket-meta">${esc(e.ts)}</td><td><code>${esc(e.event_type)}</code></td><td>${esc(e.message)}</td></tr>`
|
|
).join('');
|
|
el.innerHTML = `
|
|
<div class="toolbar agentic-toolbar">
|
|
<div><h2>Agentic Ops</h2><p class="ticket-meta">Spec 029 · ${tier} ${ollama}</p></div>
|
|
<button type="button" class="btn btn-primary btn-sm" id="btn-agentic-refresh">Actualizar</button>
|
|
</div>
|
|
<div class="agentic-grid">
|
|
<div class="card"><h3>Cenários</h3>
|
|
<table class="data-table"><thead><tr><th>ID</th><th>Título</th><th>Último</th><th>Quando</th></tr></thead><tbody>${sRows}</tbody></table>
|
|
</div>
|
|
<div class="agentic-findings-col"><h3>Findings</h3>${fRows}</div>
|
|
</div>
|
|
<section class="card agentic-chat-card" style="margin-top:1rem">
|
|
<h3>Copiloto Ops (T1)</h3>
|
|
<p class="ticket-meta">Pergunte sobre infra, VM123, findings ou procedimentos — resposta contextual pt-BR.</p>
|
|
<div class="agentic-chat-box">
|
|
<textarea id="agentic-chat-input" rows="3" placeholder="Ex.: O que fazer se Ollama VM123 estiver offline?" class="input"></textarea>
|
|
<button type="button" class="btn btn-primary btn-sm" id="btn-agentic-chat">Perguntar</button>
|
|
</div>
|
|
<div id="agentic-chat-answer" class="agentic-chat-answer" hidden></div>
|
|
</section>
|
|
<section class="card" style="margin-top:1rem"><h3>Audit log</h3>
|
|
<table class="data-table data-table-compact"><thead><tr><th>Quando</th><th>Evento</th><th>Mensagem</th></tr></thead><tbody>${lRows}</tbody></table>
|
|
</section>`;
|
|
el.querySelector('#btn-agentic-refresh')?.addEventListener('click', renderAgenticOps);
|
|
el.querySelectorAll('[data-ack]').forEach(btn => btn.addEventListener('click', async () => {
|
|
await api(`/findings/${btn.dataset.ack}/ack`, { method: 'POST' });
|
|
await renderAgenticOps();
|
|
}));
|
|
el.querySelector('#btn-agentic-chat')?.addEventListener('click', async () => {
|
|
const input = el.querySelector('#agentic-chat-input');
|
|
const out = el.querySelector('#agentic-chat-answer');
|
|
const q = (input?.value || '').trim();
|
|
if (!q) return;
|
|
out.hidden = false;
|
|
out.innerHTML = '<p class="loading">A pensar…</p>';
|
|
try {
|
|
const res = await sendChat(q);
|
|
out.innerHTML = `<p><strong>Resposta</strong> <span class="ticket-meta">(${esc(res.model)})</span></p><p>${esc(res.answer)}</p>`;
|
|
} catch (err) {
|
|
out.innerHTML = `<p class="error">${esc(err.message)}</p>`;
|
|
}
|
|
});
|
|
} catch (err) {
|
|
el.innerHTML = `<p class="error">Erro: ${esc(err.message)}</p>`;
|
|
}
|
|
}
|
|
window.renderAgenticOps = renderAgenticOps;
|
|
})();
|