refactor: redesign MCP Servers menu for clearer layout

Replace confusing table+upload card layout with individual server cards,
radio button type selector with dynamic fields, and inline actions per server.
This commit is contained in:
nogueiraguh
2026-03-03 10:19:37 -03:00
parent 77a4824907
commit 7e69ad8685

View File

@@ -400,46 +400,67 @@ async function tGenai(id){try{const d=await $api('/genai/test/'+id,{method:'POST
async function dGenai(id){if(!confirm('Excluir modelo?'))return;await $api('/genai/configs/'+id,{method:'DELETE'});S.genaiCfg=await $api('/genai/configs');R()}
/* ── MCP Servers ── */
function rMCP(){const adbOpts=S.adbCfg.map(a=>`<option value="${a.id}">${a.config_name} (${a.dsn})</option>`).join('');
function mcpTypeFields(){const t=document.getElementById('mt')?.value;
document.querySelectorAll('.mcp-f').forEach(el=>{el.style.display='none'});
if(t==='stdio'){document.getElementById('mf-cmd').style.display='';document.getElementById('mf-args').style.display=''}
if(t==='sse')document.getElementById('mf-url').style.display='';
if(t==='module'){document.getElementById('mf-cmd').style.display='';document.getElementById('mf-args').style.display=''}}
function rMCP(){const adbOpts=S.adbCfg.map(a=>`<option value="${a.id}">${a.config_name}</option>`).join('');
return`<div class="cd"><div class="ct">🔌 Servidores Registrados</div>
<table><thead><tr><th>Nome</th><th>Tipo</th><th>Cmd / URL</th><th>ADB Link</th><th>Status</th><th>Ações</th></tr></thead><tbody>
${S.mcpSvr.map(m=>{const adb=m.linked_adb_id?S.adbCfg.find(a=>a.id===m.linked_adb_id):null;
return`<tr><td><strong>${m.name}</strong><br><span style="font-size:.66rem;color:var(--t4)">${m.description||''}</span></td>
<td><span class="tag">${m.server_type}</span></td>
<td style="font-family:var(--fm);font-size:.66rem;color:var(--t4)">${m.command||m.url||m.module_path||'—'}</td>
<td>${adb?`<span class="badge b-pass">${adb.config_name}</span>`:'<span style="color:var(--t4)">—</span>'}</td>
<td><span class="badge ${m.is_active?'b-on':'b-off'}">${m.is_active?'Ativo':'Inativo'}</span></td>
<td><button class="btn bs bsm" onclick="tgMcp('${m.id}')">${m.is_active?'Desativar':'Ativar'}</button>
<button class="btn bd bsm" onclick="dMcp('${m.id}')">Excluir</button></td></tr>`}).join('')}</tbody></table>
${!S.mcpSvr.length?'<div class="emp" style="padding:.85rem"><p>Nenhum MCP server registrado.</p></div>':''}</div>
<div class="cd"><div class="ct"> Registrar MCP Server</div>
<div class="cdesc">Registre servidores MCP para execução de tarefas e integração com ferramentas.</div><div id="mcm"></div>
<div class="cdesc">MCP servers disponíveis para integração com ferramentas e execução de tarefas.</div>
${S.mcpSvr.length?S.mcpSvr.map(m=>{const adb=m.linked_adb_id?S.adbCfg.find(a=>a.id===m.linked_adb_id):null;
return`<div style="border:1px solid var(--bd);border-radius:var(--r);padding:1rem;margin-bottom:.75rem;background:var(--bg2)">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:.65rem">
<div><strong style="font-size:.88rem">${m.name}</strong> <span class="tag" style="margin-left:.4rem">${m.server_type}</span>
<span class="badge ${m.is_active?'b-on':'b-off'}" style="margin-left:.35rem">${m.is_active?'Ativo':'Inativo'}</span></div>
<div style="display:flex;gap:.35rem"><button class="btn bs bsm" onclick="tgMcp('${m.id}')">${m.is_active?'Desativar':'Ativar'}</button>
<button class="btn bd bsm" onclick="dMcp('${m.id}')">Excluir</button></div></div>
${m.description?`<div style="font-size:.72rem;color:var(--t3);margin-bottom:.5rem">${m.description}</div>`:''}
<div style="display:flex;gap:1.5rem;flex-wrap:wrap;font-size:.72rem;color:var(--t4)">
${m.command?`<div><span style="color:var(--t3);font-weight:600">Comando:</span> <code style="font-family:var(--fm);font-size:.66rem">${m.command}</code></div>`:''}
${m.url?`<div><span style="color:var(--t3);font-weight:600">URL:</span> <code style="font-family:var(--fm);font-size:.66rem">${m.url}</code></div>`:''}
${m.module_path?`<div><span style="color:var(--t3);font-weight:600">Módulo:</span> <code style="font-family:var(--fm);font-size:.66rem">${m.module_path}</code></div>`:''}
<div><span style="color:var(--t3);font-weight:600">ADB:</span> ${adb?`<span class="badge b-pass">${adb.config_name}</span>`:'<span>Nenhum</span>'}</div></div>
<div style="display:flex;gap:.5rem;margin-top:.65rem;align-items:center;flex-wrap:wrap">
<input type="file" id="muf_${m.id}" accept=".py" style="font-size:.68rem;max-width:200px">
<button class="btn bs bsm" onclick="uMcp('${m.id}')">Upload .py</button>
<select id="mlnk_${m.id}" style="font-size:.72rem;padding:.28rem .5rem;border-radius:6px;border:1px solid var(--bd)">
<option value="">Sem ADB</option>${adbOpts}</select>
<button class="btn bs bsm" onclick="lnkMcp('${m.id}')">Vincular ADB</button></div></div>`}).join('')
:'<div class="emp" style="padding:.85rem"><p>Nenhum MCP server registrado.</p></div>'}</div>
<div class="cd"><div class="ct"> Registrar Novo Server</div>
<div class="cdesc">Configure um novo servidor MCP. Os campos se ajustam ao tipo selecionado.</div><div id="mcm"></div>
<div class="g2"><div class="ig"><label>Nome</label><input type="text" id="mn" placeholder="CIS Benchmark Server"></div>
<div class="ig"><label>Descrição</label><input type="text" id="md" placeholder="Executa checks CIS 3.0"></div></div>
<div class="g3"><div class="ig"><label>Tipo</label><select id="mt"><option value="stdio">stdio</option><option value="sse">SSE (HTTP)</option><option value="module">Python Module</option></select></div>
<div class="ig"><label>Comando (stdio)</label><input type="text" id="mcmd" placeholder="python3 server.py"></div>
<div class="ig"><label>URL (SSE)</label><input type="text" id="murl" placeholder="http://localhost:8001/sse"></div></div>
<div class="g2"><div class="ig"><label>🗄️ Vincular ADB Vector</label><div class="ht">MCP server usará este banco como ferramenta</div><select id="madb"><option value="">Nenhum</option>${adbOpts}</select></div>
<div class="ig"><label>Args (JSON)</label><input type="text" id="margs" placeholder='["--config", "/path"]'></div></div>
<button class="btn bp" onclick="sMcp()">Registrar Server</button></div>
<div class="cd"><div class="ct">📤 Upload & Vínculo</div>
<div class="g3"><div class="ig"><label>MCP Server</label><select id="mup">${S.mcpSvr.map(m=>`<option value="${m.id}">${m.name}</option>`).join('')}</select></div>
<div class="ig"><label>Script .py</label><input type="file" id="muf" accept=".py">
<button class="btn bs bsm" style="margin-top:.35rem" onclick="uMcp()">Upload</button></div>
<div class="ig"><label>Vincular ADB</label><select id="mlnk"><option value="">Nenhum</option>${adbOpts}</select>
<button class="btn bs bsm" style="margin-top:.35rem" onclick="lnkMcp()">Vincular</button></div></div></div>`}
async function sMcp(){try{let args=null;const a=document.getElementById('margs').value;if(a)try{args=JSON.parse(a)}catch(e){}
<div class="ig" style="margin-bottom:.75rem"><label>Tipo de Servidor</label>
<div style="display:flex;gap:.5rem;margin-top:.3rem">
<label style="display:flex;align-items:center;gap:.3rem;padding:.4rem .75rem;border:1px solid var(--bd);border-radius:8px;cursor:pointer;font-size:.78rem;transition:all .2s">
<input type="radio" name="mt" value="stdio" checked onchange="mcpTypeFields()"> <span>⌨️ stdio</span></label>
<label style="display:flex;align-items:center;gap:.3rem;padding:.4rem .75rem;border:1px solid var(--bd);border-radius:8px;cursor:pointer;font-size:.78rem;transition:all .2s">
<input type="radio" name="mt" value="sse" onchange="mcpTypeFields()"> <span>🌐 SSE (HTTP)</span></label>
<label style="display:flex;align-items:center;gap:.3rem;padding:.4rem .75rem;border:1px solid var(--bd);border-radius:8px;cursor:pointer;font-size:.78rem;transition:all .2s">
<input type="radio" name="mt" value="module" onchange="mcpTypeFields()"> <span>🐍 Python Module</span></label></div></div>
<div id="mf-cmd" class="mcp-f"><div class="ig"><label>Comando</label><div class="ht">Comando para iniciar o servidor</div>
<input type="text" id="mcmd" placeholder="python3 server.py"></div></div>
<div id="mf-url" class="mcp-f" style="display:none"><div class="ig"><label>URL do Servidor</label><div class="ht">Endpoint SSE do servidor MCP</div>
<input type="text" id="murl" placeholder="http://localhost:8001/sse"></div></div>
<div id="mf-args" class="mcp-f"><div class="ig"><label>Argumentos (JSON)</label><div class="ht">Array de argumentos passados ao comando</div>
<input type="text" id="margs" placeholder='["--config", "/path/to/config"]'></div></div>
<div class="ig" style="margin-top:.5rem"><label>🗄️ Vincular ADB Vector</label><div class="ht">Opcional — o MCP server poderá usar este banco como ferramenta.</div>
<select id="madb"><option value="">Nenhum</option>${adbOpts}</select></div>
<button class="btn bp" style="margin-top:.75rem" onclick="sMcp()">Registrar Server</button></div>`}
async function sMcp(){try{let args=null;const a=document.getElementById('margs')?.value;if(a)try{args=JSON.parse(a)}catch(e){}
const mt=document.querySelector('input[name="mt"]:checked')?.value||'stdio';
await $api('/mcp/servers',{method:'POST',body:{name:document.getElementById('mn').value,description:document.getElementById('md').value,
server_type:document.getElementById('mt').value,command:document.getElementById('mcmd').value||null,url:document.getElementById('murl').value||null,args,
server_type:mt,command:document.getElementById('mcmd')?.value||null,url:document.getElementById('murl')?.value||null,args,
linked_adb_id:document.getElementById('madb').value||null}});
S.mcpSvr=await $api('/mcp/servers');sm('mcm','✅ Registrado!','s');R()}catch(e){sm('mcm',e.message,'e')}}
async function tgMcp(id){await $api('/mcp/servers/'+id+'/toggle',{method:'PUT'});S.mcpSvr=await $api('/mcp/servers');R()}
async function dMcp(id){if(!confirm('Excluir server?'))return;await $api('/mcp/servers/'+id,{method:'DELETE'});S.mcpSvr=await $api('/mcp/servers');R()}
async function uMcp(){const fd=new FormData();const f=document.getElementById('muf').files[0];if(!f)return alert('Selecione um arquivo');
fd.append('file',f);const id=document.getElementById('mup').value;if(!id)return alert('Selecione um server');
try{await $api('/mcp/servers/'+id+'/upload',{method:'POST',body:fd,headers:{}});alert('Upload realizado!')}catch(e){alert(e.message)}}
async function lnkMcp(){const mid=document.getElementById('mup').value;const aid=document.getElementById('mlnk').value;if(!mid)return alert('Selecione MCP');
try{await $api('/mcp/servers/'+mid+'/link-adb?adb_id='+(aid||''),{method:'PUT'});S.mcpSvr=await $api('/mcp/servers');alert('Vinculado!');R()}catch(e){alert(e.message)}}
async function uMcp(id){const fd=new FormData();const f=document.getElementById('muf_'+id)?.files[0];if(!f)return alert('Selecione um arquivo .py');
fd.append('file',f);try{await $api('/mcp/servers/'+id+'/upload',{method:'POST',body:fd,headers:{}});alert('Upload realizado!')}catch(e){alert(e.message)}}
async function lnkMcp(id){const aid=document.getElementById('mlnk_'+id)?.value||'';
try{await $api('/mcp/servers/'+id+'/link-adb?adb_id='+aid,{method:'PUT'});S.mcpSvr=await $api('/mcp/servers');alert('Vinculado!');R()}catch(e){alert(e.message)}}
/* ── ADB Vector ── */
function rADB(){return`<div class="cd"><div class="ct">🗄️ Conexões Registradas</div>