Files
Pulse Agent adeb4dad33 feat: pulse-dev — dev environment full-stack com hot reload + agentes + taskboard + vault Obsidian
- Stack Swarm 'dev': redis + taskboard + frontend HMR (Vite) + backend HMR (tsx) + 5 agents
- TaskBoard standalone React com Redis pub/sub e BLPOP queue
- Backend API Express: /tasks /agents /health com hot reload
- Agentes: 2 FE (frontend), 2 BE (backend), 1 DevOps (parallel workers)
- Vault Obsidian: /root/Obsidian-Pulse/ com estrutura Inbox/Projetos/Docker/Dev/Codex/Logs/Memorias/Templates
- Skill obsidian-vault-linker instalada e documentada
- Caddy labels: board.octal.tec.br + api.octal.tec.br + frontend.octal.tec.br
- Protocolo task queue Redis documentado em MEMORY.md
2026-05-20 19:40:54 -03:00

130 lines
6.3 KiB
HTML

<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Pulse Dev — Frontend</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #0f1117; color: #e4e4e7; }
nav { background: #1e293b; padding: 12px 24px; display: flex; gap: 20px; border-bottom: 1px solid #334155; }
nav a { color: #94a3b8; text-decoration: none; font-size: 14px; }
nav a.active { color: #60a5fa; font-weight: 600; }
section { padding: 32px 24px; max-width: 960px; margin: 0 auto; }
h1 { font-size: 28px; margin-bottom: 8px; }
h2 { font-size: 18px; margin: 24px 0 12px; color: #60a5fa; }
p { color: #94a3b8; line-height: 1.7; font-size: 14px; }
.status-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-top: 16px; }
.card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 16px; }
.card b { color: #60a5fa; font-size: 20px; }
.card small { color: #64748b; font-size: 12px; }
.badge { display: inline-block; padding: 2px 8px; border-radius: 99px; font-size: 11px; margin: 2px; }
.ok { background: #14532d; color: #86efac; }
.warn{ background: #78350f; color: #fde68a; }
.err { background: #7f1d1d; color: #fca5a5; }
pre { background: #0f1117; border: 1px solid #334155; border-radius: 6px; padding: 16px; font-size: 13px; overflow-x: auto; color: #a5f3fc; }
</style>
</head>
<body>
<nav>
<a href="#overview">Visão Geral</a>
<a href="#agents">Agentes</a>
<a href="#stacks">Stacks</a>
<a href="#flows">Fluxos</a>
</nav>
<section id="overview">
<h1>⚡ Pulse Frontend</h1>
<p>Ambiente de desenvolvimento full-stack com hot reload, agents paralelos e taskboard em tempo real.</p>
<div class="status-grid">
<div class="card"><small>Redis</small><br><b id="redis-badge"><span class="warn badge">conectando…</span></b></div>
<div class="card"><small>TaskBoard</small><br><b>board.octal.tec.br</b></div>
<div class="card"><small>Backend API</small><br><b id="api-status">verificando…</b></div>
<div class="card"><small>Hot Reload</small><br><b id="hmr-status">aguardando</b></div>
<div class="card"><small>Backend Agent</small><br><b id="be-status"></b></div>
<div class="card"><small>Frontend Agent</small><br><b id="fe-status"></b></div>
<div class="card"><small>DevOps Agent</small><br><b id="ops-status"></b></div>
<div class="card"><small>Tasks na Queue</small><br><b id="queue-count"></b></div>
</div>
<h2>Stack Swarm / Clusters</h2>
<pre id="stack-info">Carregando…</pre>
<h2>Últimos Logs</h2>
<pre id="logs">Aguardando eventos…</pre>
</section>
<script>
const API = 'http://redis:6379'
const BACKEND_URL = 'http://localhost:3001'
async function update() {
try {
const [agentsRes, stacksRes, logsRes] = await Promise.allSettled([
fetch(`${BACKEND_URL}/agents`),
fetch(`${BACKEND_URL}/tasks?redis=1`),
fetch(`${BACKEND_URL}/health`),
])
if (agentsRes.status === 'fulfilled') {
const agents = await agentsRes.value.json()
const map = {};
agents.forEach(a => { map[a.id] = a; })
const setStatus = (id, badgeId, labelId) => {
const el = document.getElementById(badgeId)
if (!el) return
const a = map[id]
if (!a) return
const s = a.status
document.getElementById(labelId).textContent = a.role || s
el.innerHTML = `<span class="${s==='online'?'ok':s==='busy'?'warn':'err'} badge">${s}</span>`
}
setStatus('dev-backend', 'be-status', 'api-status')
setStatus('agent-frontend','fe-status','hmr-status')
setStatus('agent-devops', 'ops-status','ops-status')
}
if (stacksRes.status === 'fulfilled') {
const tasks = await stacksRes.value.json()
const counts = { todo: 0, doing: 0, done: 0 }
tasks.forEach(t => { if (counts[t.status] !== undefined) counts[t.status]++ })
document.getElementById('queue-count').textContent = counts.todo + counts.doing
}
} catch(e) {}
}
async function getStackInfo() {
const r = await fetch(`${BACKEND_URL}/health`)
if (!r.ok) return 'Backend: timeout — verificar stack dev no Swarm'
return`Stack Swarm — Ambiente Dev Full-Stack
┌─────────────────────────────────────────────────────────────────┐
│ ORQUESTRADOR │ TaskBoard │ Redis │ Pipeline │
│ dock.pulse-ops│ board.oc. │ 6379 │ CI/CD → Gitea │
│ (auto) │ (nginx) │ (falback)│ GitHub Actions │
├─────────────────────────────────────────────────────────────────┤
│ FRONTEND │ BACKEND │ WORKERS │ Deploy │
│ Vite 5173 │ tsx:3001 │ 2 FE/2 BE│ docker stack deploy -c │
│ (hot reload) │ (hot reload│ 1 DevOps │ runbooks/dev-stack.yml │
│ │ + HMR) │ │ dev │
└─────────────────────────────────────────────────────────────────┘`
}
async function getLogs() {
try {
const r = await fetch(`${BACKEND_URL}/logs?limit=20`)
const lines = await r.json()
return lines.map(l => {
const ts = ts(l.ts || Date.now())
const col = { INFO:'#60a5fa', AGENT:'#22c55e', TASK:'#a78bfa', WARN:'#f0b429', ERROR:'#ef4444' }[l.level] || '#94a3b8'
return `\x1b[${col}m[${ts}]\x1b[0m [\x1b[${col}m${l.level}\x1b[0m] ${l.msg || JSON.stringify(l)}`
}).join('\n') || 'Sem logs ainda...'
} catch(e) { return 'Logs indisponíveis' }
}
function ts(ms) {
const d = new Date(ms)
return `${d.getHours().toString().padStart(2,'0')}:${d.getMinutes().toString().padStart(2,'0')}:${d.getSeconds().toString().padStart(2,'0')}`
}
document.addEventListener('DOMContentLoaded', async () => {
document.getElementById('stack-info').textContent = await getStackInfo()
await update()
setInterval(update, 3000)
})
</script>