feat: add RAG pipeline with OCI GenAI embeddings and ADB vector search
Integrate Retrieval-Augmented Generation into the chat flow using OCI
GenAI embed_text API and Oracle Autonomous Database vector storage.
The chat automatically queries ADB for relevant context when an active
ADB config with a linked GenAI config exists.
- Add EMBEDDING_MODELS catalog (Cohere Embed v3.0/light)
- Add _embed_text() using OCI GenAI SDK embed endpoint
- Add _vector_search() with VECTOR_DISTANCE cosine similarity
- Add _get_adb_connection(), _ensure_embeddings_table(), _build_rag_context()
- Add document ingestion endpoint (POST /api/adb/{vid}/ingest)
- Add table creation endpoint (POST /api/adb/{vid}/ensure-table)
- Modify chat endpoint with automatic RAG augmentation (non-fatal)
- Add GenAI config and embedding model selectors to ADB UI
- Add RAG status indicator in chat toolbar
- Add document ingestion section in ADB tab
This commit is contained in:
@@ -194,7 +194,7 @@ const V='1.1';
|
||||
const LOGO_W=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="26" height="26" style="flex-shrink:0"><rect x="2" y="5" width="32" height="26" rx="13" ry="13" fill="rgba(255,255,255,0.12)" stroke="#fff" stroke-width="2"/><rect x="11" y="11" width="14" height="14" rx="4" fill="#fff" opacity="0.95"/><circle cx="15" cy="17" r="2" fill="#C74634"/><circle cx="21" cy="17" r="2" fill="#C74634"/><circle cx="15" cy="16.7" r="0.7" fill="#fff"/><circle cx="21" cy="16.7" r="0.7" fill="#fff"/><rect x="14" y="21" width="8" height="1.5" rx="0.75" fill="#C74634" opacity="0.6"/><line x1="18" y1="11" x2="18" y2="6" stroke="#fff" stroke-width="1.2" stroke-linecap="round"/><circle cx="18" cy="5.5" r="1.5" fill="#fff" opacity="0.9"/><line x1="11" y1="18" x2="6" y2="18" stroke="#fff" stroke-width="1" stroke-linecap="round" opacity="0.7"/><line x1="25" y1="18" x2="30" y2="18" stroke="#fff" stroke-width="1" stroke-linecap="round" opacity="0.7"/><circle cx="5.5" cy="18" r="1" fill="#fff" opacity="0.7"/><circle cx="30.5" cy="18" r="1" fill="#fff" opacity="0.7"/></svg>`;
|
||||
const LOGO_R=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="52" height="52"><rect x="2" y="5" width="32" height="26" rx="13" ry="13" fill="rgba(199,70,52,0.08)" stroke="#C74634" stroke-width="1.8"/><rect x="11" y="11" width="14" height="14" rx="4" fill="#fff" stroke="#C74634" stroke-width="0.4"/><circle cx="15" cy="17" r="2" fill="#C74634"/><circle cx="21" cy="17" r="2" fill="#C74634"/><circle cx="15" cy="16.7" r="0.7" fill="#fff"/><circle cx="21" cy="16.7" r="0.7" fill="#fff"/><rect x="14" y="21" width="8" height="1.5" rx="0.75" fill="#C74634" opacity="0.6"/><line x1="18" y1="11" x2="18" y2="6" stroke="#C74634" stroke-width="1.2" stroke-linecap="round"/><circle cx="18" cy="5.5" r="1.5" fill="#C74634" opacity="0.25" stroke="#C74634" stroke-width="0.7"/><line x1="11" y1="18" x2="6" y2="18" stroke="#C74634" stroke-width="1" stroke-linecap="round" opacity="0.4"/><line x1="25" y1="18" x2="30" y2="18" stroke="#C74634" stroke-width="1" stroke-linecap="round" opacity="0.4"/><circle cx="5.5" cy="18" r="1" fill="#C74634" opacity="0.35"/><circle cx="30.5" cy="18" r="1" fill="#C74634" opacity="0.35"/></svg>`;
|
||||
|
||||
const S={user:null,token:null,tab:'chat',msgs:[],sid:null,reports:[],ociCfg:[],genaiCfg:[],adbCfg:[],mcpSvr:[],users:[],auditLogs:[],models:{},regions:[],selGenai:'',expData:null};
|
||||
const S={user:null,token:null,tab:'chat',msgs:[],sid:null,reports:[],ociCfg:[],genaiCfg:[],adbCfg:[],mcpSvr:[],users:[],auditLogs:[],models:{},regions:[],embModels:{},selGenai:'',expData:null};
|
||||
const API='/api';
|
||||
|
||||
async function $api(p,o={}){const h={...(o.headers||{})};if(S.token)h['Authorization']='Bearer '+S.token;
|
||||
@@ -208,7 +208,7 @@ async function doLogout(){try{await $api('/auth/logout',{method:'POST'})}catch(e
|
||||
async function checkAuth(){const t=localStorage.getItem('t');if(!t)return;S.token=t;try{S.user=await $api('/users/me');await loadData()}catch(e){S.token=null;localStorage.removeItem('t')}}
|
||||
async function loadData(){try{
|
||||
[S.ociCfg,S.reports,S.genaiCfg,S.mcpSvr]=await Promise.all([$api('/oci/configs'),$api('/reports'),$api('/genai/configs'),$api('/mcp/servers')]);
|
||||
try{const m=await $api('/genai/models');S.models=m.models;S.regions=m.regions}catch(e){}
|
||||
try{const m=await $api('/genai/models');S.models=m.models;S.regions=m.regions;S.embModels=m.embedding_models||{}}catch(e){}
|
||||
try{S.adbCfg=await $api('/adb/configs')}catch(e){S.adbCfg=[]}
|
||||
if(S.user.role==='admin'){try{S.users=await $api('/users')}catch(e){}}
|
||||
}catch(e){console.error(e)}}
|
||||
@@ -235,7 +235,7 @@ function rSb(){
|
||||
const tabs=[['chat','💬','Chat Agent'],['explorer','🔍','OCI Explorer'],['report','📊','Reports'],['downloads','📁','Downloads']];
|
||||
const ctabs=[['oci-config','☁️','Credenciais OCI'],['genai','🧠','GenAI Config'],['mcp','🔌','MCP Servers'],['adb','🗄️','ADB Vector']];
|
||||
const atabs=[['users','👥','Usuários'],['mfa','🔐','MFA'],['audit','📋','Audit Log']];
|
||||
const i=(S.user?.username||'?')[0].toUpperCase();
|
||||
const i=(S.user?.first_name||S.user?.username||'?')[0].toUpperCase();
|
||||
return`<div class="sb">
|
||||
<div class="sb-h"><h1>${LOGO_W} OCI CIS Agent</h1><div class="st">Infrastructure & Security · v${V}</div></div>
|
||||
<div class="nav"><div class="nl">Principal</div>
|
||||
@@ -244,7 +244,7 @@ ${tabs.map(t=>`<div class="ni ${S.tab===t[0]?'on':''}" onclick="switchTab('${t[0
|
||||
${ctabs.map(t=>`<div class="ni ${S.tab===t[0]?'on':''}" onclick="switchTab('${t[0]}')"><span class="ic">${t[1]}</span>${t[2]}</div>`).join('')}
|
||||
${S.user?.role==='admin'?`<div class="nl">Administração</div>
|
||||
${atabs.map(t=>`<div class="ni ${S.tab===t[0]?'on':''}" onclick="switchTab('${t[0]}')"><span class="ic">${t[1]}</span>${t[2]}</div>`).join('')}`:''}</div>
|
||||
<div class="sb-f"><div class="ui"><div class="ua">${i}</div><div><div class="un">${S.user?.username}</div><div class="ur">${S.user?.role}</div></div>
|
||||
<div class="sb-f"><div class="ui"><div class="ua">${i}</div><div><div class="un">${S.user?.first_name?S.user.first_name+' '+S.user.last_name:S.user?.username}</div><div class="ur">${S.user?.role}</div></div>
|
||||
<div class="lo-btn" onclick="doLogout()" title="Sair">⏻</div></div></div></div>`}
|
||||
|
||||
function rTb(){const t={'chat':'💬 AI Agent Chat','explorer':'🔍 OCI Account Explorer','report':'📊 Compliance Reports','downloads':'📁 Downloads','oci-config':'☁️ Credenciais OCI','genai':'🧠 OCI Generative AI','mcp':'🔌 MCP Servers','adb':'🗄️ Autonomous DB Vector','users':'👥 Gerenciar Usuários','mfa':'🔐 Autenticação MFA','audit':'📋 Audit Log'};
|
||||
@@ -259,6 +259,7 @@ function rChat(){
|
||||
const gcOpts=S.genaiCfg.map(g=>{const mi=S.models[g.model_id];return`<option value="${g.id}" ${S.selGenai===g.id?'selected':''}>${g.name||mi?.name||g.model_id} · ${g.genai_region}</option>`}).join('');
|
||||
return`<div class="ch-c">
|
||||
<div class="ch-t"><label>🧠 Modelo:</label><select id="gsel" onchange="S.selGenai=this.value"><option value="">Agente Local (sem IA)</option>${gcOpts}</select>
|
||||
${S.adbCfg.some(c=>c.genai_config_id&&c.is_active)?'<span class="tag" style="background:var(--gnl);color:var(--gn);font-size:.62rem;margin-left:.5rem">RAG Ativo</span>':''}
|
||||
<div style="flex:1"></div><button class="btn bd bsm" onclick="clrChat()">Limpar</button></div>
|
||||
<div class="ch-m" id="chm">${ms}</div>
|
||||
<div class="ch-i"><input type="text" id="chi" placeholder="Digite sua mensagem..." onkeydown="if(event.key==='Enter')sChat()">
|
||||
@@ -442,11 +443,12 @@ async function lnkMcp(){const mid=document.getElementById('mup').value;const aid
|
||||
|
||||
/* ── ADB Vector ── */
|
||||
function rADB(){return`<div class="cd"><div class="ct">🗄️ Conexões Registradas</div>
|
||||
<table><thead><tr><th>Nome</th><th>DSN</th><th>User</th><th>Tabela</th><th>mTLS</th><th>Wallet</th><th>Ações</th></tr></thead><tbody>
|
||||
${S.adbCfg.map(c=>`<tr><td><strong>${c.config_name}</strong></td><td style="font-family:var(--fm);font-size:.68rem;color:var(--t4)">${c.dsn}</td>
|
||||
<table><thead><tr><th>Nome</th><th>DSN</th><th>User</th><th>Tabela</th><th>mTLS</th><th>Wallet</th><th>Embed</th><th>Ações</th></tr></thead><tbody>
|
||||
${S.adbCfg.map(c=>{const em=S.embModels[c.embedding_model_id];return`<tr><td><strong>${c.config_name}</strong></td><td style="font-family:var(--fm);font-size:.68rem;color:var(--t4)">${c.dsn}</td>
|
||||
<td>${c.username}</td><td><span class="tag">${c.table_name}</span></td>
|
||||
<td>${c.use_mtls?'✅':'—'}</td><td>${c.wallet_dir?'✅':'❌'}</td>
|
||||
<td><button class="btn bs bsm" onclick="tAdb('${c.id}')">Testar</button> <button class="btn bd bsm" onclick="dAdb('${c.id}')">Excluir</button></td></tr>`).join('')}</tbody></table>
|
||||
<td>${c.genai_config_id?'<span class="tag" style="background:var(--gnl);color:var(--gn)">'+(em?em.name:c.embedding_model_id)+'</span>':'<span style="color:var(--t4)">—</span>'}</td>
|
||||
<td><button class="btn bs bsm" onclick="tAdb('${c.id}')">Testar</button> <button class="btn bs bsm" onclick="ensureTable('${c.id}')">Criar Tabela</button> <button class="btn bd bsm" onclick="dAdb('${c.id}')">Excluir</button></td></tr>`}).join('')}</tbody></table>
|
||||
${!S.adbCfg.length?'<div class="emp" style="padding:.85rem"><p>Nenhuma conexão ADB registrada.</p></div>':''}</div>
|
||||
<div class="cd"><div class="ct">➕ Nova Conexão</div>
|
||||
<div class="cdesc">Conexão via <code style="font-size:.72rem">python-oracledb</code> Thin mode com Wallet (mTLS) para Oracle Autonomous Database.</div><div id="am"></div>
|
||||
@@ -455,40 +457,115 @@ ${!S.adbCfg.length?'<div class="emp" style="padding:.85rem"><p>Nenhuma conexão
|
||||
<div class="ig"><label>Username</label><input type="text" id="auser" placeholder="ADMIN"></div>
|
||||
<div class="ig"><label>Password</label><input type="password" id="apw"></div>
|
||||
<div class="ig"><label>Wallet Password</label><input type="password" id="awpw" placeholder="(opcional)"></div>
|
||||
<div class="ig"><label>Tabela de Embeddings</label><input type="text" id="atbl" value="CIS_EMBEDDINGS"></div></div>
|
||||
<div class="ig"><label>Tabela de Embeddings</label><input type="text" id="atbl" value="CIS_EMBEDDINGS"></div>
|
||||
<div class="ig"><label>Config GenAI (Embeddings)</label><div class="ht">Credenciais OCI usadas para gerar embeddings via GenAI.</div>
|
||||
<select id="agcfg"><option value="">Nenhum (sem RAG)</option>${S.genaiCfg.map(g=>'<option value="'+g.id+'">'+(g.name||g.model_id)+' · '+g.genai_region+'</option>').join('')}</select></div>
|
||||
<div class="ig"><label>Modelo de Embedding</label><div class="ht">Modelo OCI GenAI para gerar vetores.</div>
|
||||
<select id="aemb">${Object.entries(S.embModels).map(([k,v])=>'<option value="'+k+'">'+v.name+' ('+v.dims+'d)</option>').join('')}</select></div></div>
|
||||
<button class="btn bp" onclick="sAdb()">Salvar Conexão</button></div>
|
||||
<div class="cd"><div class="ct">📤 Upload Wallet</div>
|
||||
<div class="g2"><div class="ig"><label>Conexão ADB</label><select id="awsel">${S.adbCfg.map(c=>`<option value="${c.id}">${c.config_name}</option>`).join('')}</select></div>
|
||||
<div class="ig"><label>Wallet ZIP</label><input type="file" id="awf" accept=".zip"></div></div>
|
||||
<button class="btn bs" onclick="uWallet()">Upload Wallet</button></div>`}
|
||||
<button class="btn bs" onclick="uWallet()">Upload Wallet</button></div>
|
||||
<div class="cd"><div class="ct">📄 Ingerir Documentos</div>
|
||||
<div class="cdesc">Gere embeddings e insira documentos na tabela ADB para uso com RAG.</div>
|
||||
<div class="g2"><div class="ig"><label>Conexão ADB</label>
|
||||
<select id="aisel">${S.adbCfg.filter(c=>c.genai_config_id).map(c=>'<option value="'+c.id+'">'+c.config_name+'</option>').join('')}</select></div>
|
||||
<div class="ig"><label>Documentos (JSON)</label>
|
||||
<div class="ht">Array de objetos: [{"content":"texto","source":"fonte","metadata":"info"}]</div>
|
||||
<textarea id="adocs" rows="6" style="font-family:var(--fm);font-size:.72rem;width:100%;padding:.5rem;border:1px solid var(--bd);border-radius:var(--r)" placeholder='[{"content":"...","source":"doc.pdf","metadata":"page 1"}]'></textarea></div></div>
|
||||
<button class="btn bp" onclick="ingestDocs()">Ingerir Documentos</button></div>`}
|
||||
async function sAdb(){try{await $api('/adb/config',{method:'POST',body:{config_name:document.getElementById('an').value,dsn:document.getElementById('adsn').value,
|
||||
username:document.getElementById('auser').value,password:document.getElementById('apw').value,
|
||||
wallet_password:document.getElementById('awpw').value||null,table_name:document.getElementById('atbl').value}});
|
||||
wallet_password:document.getElementById('awpw').value||null,table_name:document.getElementById('atbl').value,
|
||||
genai_config_id:document.getElementById('agcfg').value||null,
|
||||
embedding_model_id:document.getElementById('aemb').value||'cohere.embed-multilingual-v3.0'}});
|
||||
S.adbCfg=await $api('/adb/configs');sm('am','✅ Conexão salva!','s');R()}catch(e){sm('am',e.message,'e')}}
|
||||
async function tAdb(id){try{const d=await $api('/adb/test/'+id,{method:'POST'});alert(d.status==='success'?'✅ '+d.message:'❌ '+d.message)}catch(e){alert(e.message)}}
|
||||
async function dAdb(id){if(!confirm('Excluir conexão?'))return;await $api('/adb/configs/'+id,{method:'DELETE'});S.adbCfg=await $api('/adb/configs');R()}
|
||||
async function uWallet(){const fd=new FormData();const f=document.getElementById('awf').files[0];if(!f)return alert('Selecione wallet ZIP');
|
||||
fd.append('wallet',f);const id=document.getElementById('awsel').value;if(!id)return alert('Selecione conexão');
|
||||
try{const d=await $api('/adb/'+id+'/upload-wallet',{method:'POST',body:fd,headers:{}});alert('Wallet enviada! Arquivos: '+d.files.join(', '))}catch(e){alert(e.message)}}
|
||||
async function ensureTable(id){try{const d=await $api('/adb/'+id+'/ensure-table',{method:'POST'});alert('Tabela criada/verificada: '+d.table)}catch(e){alert('Erro: '+e.message)}}
|
||||
async function ingestDocs(){const id=document.getElementById('aisel').value;
|
||||
if(!id)return alert('Selecione conexão ADB com GenAI configurado');
|
||||
let docs;try{docs=JSON.parse(document.getElementById('adocs').value)}catch(e){return alert('JSON inválido')}
|
||||
if(!Array.isArray(docs)||!docs.length)return alert('Forneça array de documentos');
|
||||
try{const d=await $api('/adb/'+id+'/ingest',{method:'POST',body:{adb_config_id:id,documents:docs}});alert(d.message)}catch(e){alert('Erro: '+e.message)}}
|
||||
|
||||
/* ── Users ── */
|
||||
function rUsers(){return`<div class="cd"><div class="ct">👥 Usuários</div>
|
||||
<table><thead><tr><th>Usuário</th><th>Email</th><th>Role</th><th>MFA</th><th>Status</th><th>Último Login</th><th>Ações</th></tr></thead><tbody>
|
||||
${S.users.map(u=>`<tr><td><strong>${u.username}</strong></td><td>${u.email||'—'}</td>
|
||||
<td><span class="badge b-${u.role}">${u.role}</span></td><td>${u.mfa_enabled?'✅':'❌'}</td>
|
||||
<td><span class="badge ${u.is_active?'b-pass':'b-fail'}">${u.is_active?'Ativo':'Inativo'}</span></td>
|
||||
function rUsers(){return`<div class="cd"><div class="ct">👥 Gerenciamento de Usuários</div><div id="um"></div>
|
||||
<table style="margin-top:.65rem"><thead><tr><th>Nome</th><th>Usuário</th><th>Email</th><th>Role</th><th>MFA</th><th>Status</th><th>Último Login</th><th>Ações</th></tr></thead><tbody>
|
||||
${S.users.map(u=>`<tr>
|
||||
<td><strong>${u.first_name||''} ${u.last_name||''}</strong></td>
|
||||
<td style="font-family:var(--fm);font-size:.7rem">${u.username}</td>
|
||||
<td style="font-size:.72rem">${u.email||'—'}</td>
|
||||
<td><span class="badge b-${u.role}">${u.role}</span></td>
|
||||
<td>${u.mfa_enabled?'<span class="tag" style="background:var(--gnl);color:var(--gn)">✓</span>':'—'}</td>
|
||||
<td>${u.is_active?'<span class="tag" style="background:var(--gnl);color:var(--gn)">Ativo</span>':'<span class="tag">Inativo</span>'}</td>
|
||||
<td style="font-size:.72rem;color:var(--t4)">${u.last_login||'Nunca'}</td>
|
||||
<td><select onchange="uRole('${u.id}',this.value)" style="max-width:90px;padding:.22rem .35rem;font-size:.72rem"><option value="admin"${u.role==='admin'?' selected':''}>Admin</option>
|
||||
<option value="user"${u.role==='user'?' selected':''}>User</option><option value="viewer"${u.role==='viewer'?' selected':''}>Viewer</option></select>
|
||||
${u.is_active?` <button class="btn bd bsm" onclick="deact('${u.id}')">Desativar</button>`:''}</td></tr>`).join('')}</tbody></table></div>
|
||||
<div class="cd"><div class="ct">➕ Criar Usuário</div><div id="um"></div>
|
||||
<div class="g3"><div class="ig"><label>Usuário</label><input type="text" id="nu"></div>
|
||||
<div class="ig"><label>Email</label><input type="email" id="ne"></div>
|
||||
<div class="ig"><label>Senha</label><input type="password" id="np"></div></div>
|
||||
<div class="ig" style="max-width:180px"><label>Role</label><select id="nr"><option value="viewer">Viewer</option><option value="user">User</option><option value="admin">Admin</option></select></div>
|
||||
<td><select onchange="uRole('${u.id}',this.value)" style="max-width:90px;padding:.22rem .35rem;font-size:.72rem" ${u.username==='admin'?'disabled':''}>
|
||||
<option value="admin"${u.role==='admin'?' selected':''}>Admin</option>
|
||||
<option value="user"${u.role==='user'?' selected':''}>User</option>
|
||||
<option value="viewer"${u.role==='viewer'?' selected':''}>Viewer</option></select>
|
||||
${u.username!=='admin'&&u.is_active?` <button class="btn bd bsm" onclick="deact('${u.id}')">Desativar</button>`:''}</td></tr>`).join('')}
|
||||
</tbody></table></div>
|
||||
<div class="cd"><div class="ct">➕ Criar Novo Usuário</div><div id="um2"></div>
|
||||
<div class="g3">
|
||||
<div class="ig"><label>Nome</label><input type="text" id="nfn" placeholder="João"></div>
|
||||
<div class="ig"><label>Sobrenome</label><input type="text" id="nln" placeholder="Silva"></div>
|
||||
<div class="ig"><label>Usuário (login)</label><input type="text" id="nu" placeholder="joao.silva"></div></div>
|
||||
<div class="g3"><div class="ig"><label>Email</label><input type="email" id="ne" placeholder="joao@empresa.com"></div>
|
||||
<div class="ig"><label>Senha</label><input type="password" id="np"></div>
|
||||
<div class="ig"><label>Role</label><select id="nr"><option value="viewer">Viewer</option><option value="user">User</option><option value="admin">Admin</option></select></div></div>
|
||||
<button class="btn bp" onclick="cUser()">Criar Usuário</button></div>`}
|
||||
async function cUser(){try{await $api('/auth/register',{method:'POST',body:{username:document.getElementById('nu').value,email:document.getElementById('ne').value,
|
||||
password:document.getElementById('np').value,role:document.getElementById('nr').value}});S.users=await $api('/users');sm('um','✅ Usuário criado!','s');R()}catch(e){sm('um',e.message,'e')}}
|
||||
|
||||
async function cUser(){
|
||||
const firstName = document.getElementById('nfn').value.trim();
|
||||
const lastName = document.getElementById('nln').value.trim();
|
||||
const username = document.getElementById('nu').value.trim();
|
||||
const email = document.getElementById('ne').value.trim();
|
||||
const password = document.getElementById('np').value;
|
||||
const role = document.getElementById('nr').value;
|
||||
|
||||
if (!firstName || !lastName) {
|
||||
sm('um2', '❌ Nome e sobrenome são obrigatórios', 'e');
|
||||
return;
|
||||
}
|
||||
if (!username) {
|
||||
sm('um2', '❌ Usuário (login) é obrigatório', 'e');
|
||||
return;
|
||||
}
|
||||
if (!password) {
|
||||
sm('um2', '❌ Senha é obrigatória', 'e');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await $api('/auth/register', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
username: username,
|
||||
email: email,
|
||||
password: password,
|
||||
role: role
|
||||
}
|
||||
});
|
||||
S.users = await $api('/users');
|
||||
sm('um2', '✅ Usuário criado com sucesso!', 's');
|
||||
document.getElementById('nfn').value = '';
|
||||
document.getElementById('nln').value = '';
|
||||
document.getElementById('nu').value = '';
|
||||
document.getElementById('ne').value = '';
|
||||
document.getElementById('np').value = '';
|
||||
R();
|
||||
} catch(e) {
|
||||
sm('um2', e.message, 'e');
|
||||
}
|
||||
}
|
||||
|
||||
async function uRole(id,r){try{await $api('/users/'+id,{method:'PUT',body:{role:r}});S.users=await $api('/users')}catch(e){alert(e.message)}}
|
||||
async function deact(id){if(!confirm('Desativar usuário?'))return;await $api('/users/'+id,{method:'DELETE'});S.users=await $api('/users');R()}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user