diff --git a/backend/app.py b/backend/app.py
index 065793c..d2eb039 100644
--- a/backend/app.py
+++ b/backend/app.py
@@ -60,6 +60,13 @@ GENAI_MODELS = {
"xai.grok-3": {"provider":"xai","name":"xAI Grok 3","api_format":"GENERIC"},
}
+EMBEDDING_MODELS = {
+ "cohere.embed-english-v3.0": {"name":"Cohere Embed English v3.0","dims":1024},
+ "cohere.embed-multilingual-v3.0": {"name":"Cohere Embed Multilingual v3.0","dims":1024},
+ "cohere.embed-english-light-v3.0": {"name":"Cohere Embed English Light v3.0","dims":384},
+ "cohere.embed-multilingual-light-v3.0": {"name":"Cohere Embed Multilingual Light v3.0","dims":384},
+}
+
GENAI_REGIONS = [
"us-chicago-1","us-ashburn-1","us-phoenix-1","uk-london-1",
"eu-frankfurt-1","ap-tokyo-1","ap-osaka-1","sa-saopaulo-1",
@@ -84,7 +91,11 @@ def init_db():
with db() as c:
c.executescript("""
CREATE TABLE IF NOT EXISTS users (
- id TEXT PRIMARY KEY, username TEXT UNIQUE NOT NULL, email TEXT,
+ id TEXT PRIMARY KEY,
+ first_name TEXT NOT NULL,
+ last_name TEXT NOT NULL,
+ username TEXT UNIQUE NOT NULL,
+ email TEXT,
password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'viewer',
mfa_secret TEXT, mfa_enabled INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
@@ -171,11 +182,17 @@ def init_db():
ip TEXT, created_at TEXT DEFAULT (datetime('now'))
);
""")
+ # ── Migrations ──
+ for col in ["genai_config_id TEXT", "embedding_model_id TEXT DEFAULT 'cohere.embed-multilingual-v3.0'"]:
+ try:
+ c.execute(f"ALTER TABLE adb_vector_configs ADD COLUMN {col}")
+ except sqlite3.OperationalError:
+ pass
adm = c.execute("SELECT id FROM users WHERE username='admin'").fetchone()
if not adm:
c.execute(
- "INSERT INTO users (id,username,email,password_hash,role) VALUES (?,?,?,?,?)",
- (str(uuid.uuid4()), "admin", "admin@local", _hash_pw("admin123"), "admin")
+ "INSERT INTO users (id,first_name,last_name,username,email,password_hash,role) VALUES (?,?,?,?,?,?,?)",
+ (str(uuid.uuid4()), "Admin", "Sistema", "admin", "admin@local", _hash_pw("admin123"), "admin")
)
log.info("Default admin created: admin / admin123")
@@ -228,7 +245,7 @@ def _audit(uid, uname, action, resource=None, details=None, ip=None):
class LoginReq(BaseModel):
username: str; password: str; totp_code: Optional[str] = None
class RegisterReq(BaseModel):
- username: str; email: str; password: str; role: str = "viewer"
+ first_name: str; last_name: str; username: str; email: str; password: str; role: str = "viewer"
class TOTPVerify(BaseModel):
totp_code: str
class ChangePwReq(BaseModel):
@@ -251,6 +268,9 @@ class GenAIConfigReq(BaseModel):
class ADBVectorReq(BaseModel):
config_name: str; dsn: str; username: str; password: str
wallet_password: Optional[str] = None; table_name: str = "CIS_EMBEDDINGS"; use_mtls: bool = True
+ genai_config_id: Optional[str] = None; embedding_model_id: str = "cohere.embed-multilingual-v3.0"
+class IngestDocReq(BaseModel):
+ adb_config_id: str; documents: List[Dict[str, Any]]
class MCPServerReq(BaseModel):
name: str; description: Optional[str] = None; server_type: str = "stdio"
command: Optional[str] = None; args: Optional[List[str]] = None
@@ -274,7 +294,7 @@ async def login(req: LoginReq, request: Request):
c.execute("UPDATE users SET last_login=datetime('now') WHERE id=?", (u["id"],))
_audit(u["id"], u["username"], "login", ip=request.client.host if request.client else None)
return {"token":_make_token(u["id"],u["role"],sid),
- "user":{"id":u["id"],"username":u["username"],"email":u["email"],"role":u["role"],"mfa_enabled":bool(u["mfa_enabled"])}}
+ "user":{"id":u["id"],"first_name":u["first_name"],"last_name":u["last_name"],"username":u["username"],"email":u["email"],"role":u["role"],"mfa_enabled":bool(u["mfa_enabled"])}}
@app.post("/api/auth/logout")
async def logout_ep(u=Depends(current_user)):
@@ -287,7 +307,8 @@ async def register(req: RegisterReq, adm=Depends(require("admin"))):
uid = str(uuid.uuid4())
with db() as c:
if c.execute("SELECT 1 FROM users WHERE username=?", (req.username,)).fetchone(): raise HTTPException(400, "Usuário já existe")
- c.execute("INSERT INTO users (id,username,email,password_hash,role) VALUES (?,?,?,?,?)", (uid, req.username, req.email, _hash_pw(req.password), req.role))
+ c.execute("INSERT INTO users (id,first_name,last_name,username,email,password_hash,role) VALUES (?,?,?,?,?,?,?)",
+ (uid, req.first_name, req.last_name, req.username, req.email, _hash_pw(req.password), req.role))
_audit(adm["id"], adm["username"], "create_user", uid, f"user={req.username} role={req.role}")
return {"id": uid, "username": req.username, "role": req.role}
@@ -320,12 +341,12 @@ async def mfa_disable(user_id: str, adm=Depends(require("admin"))):
# ── Users ─────────────────────────────────────────────────────────────────────
@app.get("/api/users")
async def list_users(u=Depends(require("admin"))):
- with db() as c: rows = c.execute("SELECT id,username,email,role,mfa_enabled,is_active,created_at,last_login FROM users").fetchall()
+ with db() as c: rows = c.execute("SELECT id,first_name,last_name,username,email,role,mfa_enabled,is_active,created_at,last_login FROM users").fetchall()
return [dict(r) for r in rows]
@app.get("/api/users/me")
async def me(u=Depends(current_user)):
- return {k: u[k] for k in ("id","username","email","role","mfa_enabled")}
+ return {k: u[k] for k in ("id","first_name","last_name","username","email","role","mfa_enabled")}
@app.put("/api/users/{uid}")
async def update_user(uid: str, req: UserUpdateReq, adm=Depends(require("admin"))):
@@ -492,7 +513,7 @@ async def explore_buckets(cid: str, compartment_id: str = Query(None), u=Depends
# ── OCI GenAI Config & Chat ───────────────────────────────────────────────────
@app.get("/api/genai/models")
async def list_genai_models(u=Depends(current_user)):
- return {"models": GENAI_MODELS, "regions": GENAI_REGIONS}
+ return {"models": GENAI_MODELS, "regions": GENAI_REGIONS, "embedding_models": EMBEDDING_MODELS}
@app.post("/api/genai/config")
async def save_genai(req: GenAIConfigReq, u=Depends(require("admin","user"))):
@@ -644,6 +665,130 @@ def _call_genai(gc: dict, message: str, history: list = None) -> str:
return contents[0].text
return str(resp)
+# ── RAG Helpers ───────────────────────────────────────────────────────────────
+RAG_SYSTEM_PROMPT = """You are the OCI CIS AI Agent, an expert assistant for Oracle Cloud Infrastructure security and compliance.
+
+You have been provided with relevant context documents retrieved from a knowledge base. Use this context to provide accurate, specific answers. If the context does not contain relevant information for the question, say so and answer based on your general knowledge.
+
+Always cite the source documents when using information from the context.
+
+--- RETRIEVED CONTEXT ---
+{context}
+--- END CONTEXT ---
+
+User question: {question}"""
+
+def _get_adb_connection(cfg: dict):
+ """Create an oracledb connection from an adb_vector_configs row."""
+ import oracledb
+ params = {"user": cfg["username"], "password": _dec(cfg["password_enc"]), "dsn": cfg["dsn"]}
+ if cfg["use_mtls"] and cfg.get("wallet_dir"):
+ params["config_dir"] = cfg["wallet_dir"]
+ params["wallet_location"] = cfg["wallet_dir"]
+ if cfg.get("wallet_password_enc"):
+ params["wallet_password"] = _dec(cfg["wallet_password_enc"])
+ return oracledb.connect(**params)
+
+def _ensure_embeddings_table(cfg: dict):
+ """Create the embeddings table in ADB if it doesn't exist."""
+ table_name = cfg.get("table_name", "CIS_EMBEDDINGS")
+ emb_model = cfg.get("embedding_model_id", "cohere.embed-multilingual-v3.0")
+ dims = EMBEDDING_MODELS.get(emb_model, {}).get("dims", 1024)
+ conn = _get_adb_connection(cfg)
+ try:
+ cur = conn.cursor()
+ cur.execute("SELECT COUNT(*) FROM user_tables WHERE table_name = :1", (table_name.upper(),))
+ if cur.fetchone()[0] == 0:
+ cur.execute(f"""
+ CREATE TABLE {table_name} (
+ ID VARCHAR2(100) PRIMARY KEY,
+ CONTENT CLOB,
+ EMBEDDING VECTOR({dims}, FLOAT64),
+ METADATA VARCHAR2(4000),
+ SOURCE VARCHAR2(500),
+ CREATED_AT TIMESTAMP DEFAULT SYSTIMESTAMP
+ )
+ """)
+ conn.commit()
+ log.info(f"Created embeddings table: {table_name} (dims={dims})")
+ cur.close()
+ finally:
+ conn.close()
+
+def _embed_text(text: str, genai_cfg: dict, embedding_model_id: str) -> list:
+ """Generate embedding using OCI GenAI embed endpoint."""
+ import oci
+ config_path = str(OCI_DIR / genai_cfg["oci_config_id"] / "config")
+ config = oci.config.from_file(config_path, "DEFAULT")
+ endpoint = genai_cfg["endpoint"]
+ client = oci.generative_ai_inference.GenerativeAiInferenceClient(
+ config=config, service_endpoint=endpoint,
+ retry_strategy=oci.retry.NoneRetryStrategy(), timeout=(10, 120)
+ )
+ embed_detail = oci.generative_ai_inference.models.EmbedTextDetails()
+ embed_detail.inputs = [text]
+ embed_detail.serving_mode = oci.generative_ai_inference.models.OnDemandServingMode(model_id=embedding_model_id)
+ embed_detail.compartment_id = genai_cfg["compartment_id"]
+ embed_detail.truncate = "NONE"
+ embed_detail.input_type = "SEARCH_QUERY"
+ response = client.embed_text(embed_detail)
+ return response.data.embeddings[0]
+
+def _vector_search(cfg: dict, query_embedding: list, top_k: int = 5) -> list:
+ """Search ADB vector store using cosine similarity. Returns top-K documents."""
+ import array
+ table_name = cfg.get("table_name", "CIS_EMBEDDINGS")
+ conn = _get_adb_connection(cfg)
+ try:
+ cur = conn.cursor()
+ vec = array.array('d', query_embedding)
+ cur.execute(f"""
+ SELECT ID, CONTENT, METADATA, SOURCE,
+ VECTOR_DISTANCE(EMBEDDING, :1, COSINE) AS distance
+ FROM {table_name}
+ ORDER BY distance ASC
+ FETCH FIRST :2 ROWS ONLY
+ """, [vec, top_k])
+ results = []
+ for row in cur:
+ content = row[1]
+ if hasattr(content, 'read'):
+ content = content.read()
+ results.append({
+ "id": row[0], "content": content or "",
+ "metadata": row[2], "source": row[3], "distance": float(row[4])
+ })
+ cur.close()
+ return results
+ finally:
+ conn.close()
+
+def _build_rag_context(documents: list) -> str:
+ """Format retrieved documents into a context string for the LLM prompt."""
+ if not documents:
+ return ""
+ parts = []
+ for i, doc in enumerate(documents, 1):
+ source = doc.get("source", "unknown")
+ content = doc.get("content", "")
+ if len(content) > 2000:
+ content = content[:2000] + "..."
+ parts.append(f"[Document {i} | Source: {source}]\n{content}")
+ return "\n\n---\n\n".join(parts)
+
+def _get_active_adb_config(user_id: str) -> dict | None:
+ """Get the first active ADB vector config with a linked GenAI config."""
+ with db() as c:
+ cfg = c.execute(
+ "SELECT * FROM adb_vector_configs WHERE user_id=? AND is_active=1 AND genai_config_id IS NOT NULL ORDER BY created_at DESC LIMIT 1",
+ (user_id,)
+ ).fetchone()
+ if not cfg:
+ cfg = c.execute(
+ "SELECT * FROM adb_vector_configs WHERE is_active=1 AND genai_config_id IS NOT NULL ORDER BY created_at DESC LIMIT 1"
+ ).fetchone()
+ return dict(cfg) if cfg else None
+
# ── MCP Servers ───────────────────────────────────────────────────────────────
@app.post("/api/mcp/servers")
async def register_mcp(req: MCPServerReq, u=Depends(require("admin","user"))):
@@ -705,9 +850,10 @@ async def save_adb(req: ADBVectorReq, u=Depends(require("admin","user"))):
vid = str(uuid.uuid4())
with db() as c:
c.execute(
- "INSERT INTO adb_vector_configs (id,user_id,config_name,dsn,username,password_enc,wallet_password_enc,table_name,use_mtls) VALUES (?,?,?,?,?,?,?,?,?)",
+ "INSERT INTO adb_vector_configs (id,user_id,config_name,dsn,username,password_enc,wallet_password_enc,table_name,use_mtls,genai_config_id,embedding_model_id) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
(vid, u["id"], req.config_name, req.dsn, req.username, _enc(req.password),
- _enc(req.wallet_password) if req.wallet_password else None, req.table_name, int(req.use_mtls)))
+ _enc(req.wallet_password) if req.wallet_password else None, req.table_name, int(req.use_mtls),
+ req.genai_config_id, req.embedding_model_id))
_audit(u["id"], u["username"], "save_adb_config", vid, req.config_name)
return {"id": vid, "config_name": req.config_name}
@@ -724,8 +870,9 @@ async def upload_wallet(vid: str, wallet: UploadFile = File(...), u=Depends(requ
@app.get("/api/adb/configs")
async def list_adb(u=Depends(current_user)):
with db() as c:
- if u["role"]=="admin": rows=c.execute("SELECT id,config_name,dsn,username,table_name,use_mtls,is_active,wallet_dir,created_at FROM adb_vector_configs").fetchall()
- else: rows=c.execute("SELECT id,config_name,dsn,username,table_name,use_mtls,is_active,wallet_dir,created_at FROM adb_vector_configs WHERE user_id=?",(u["id"],)).fetchall()
+ cols = "id,config_name,dsn,username,table_name,use_mtls,is_active,wallet_dir,genai_config_id,embedding_model_id,created_at"
+ if u["role"]=="admin": rows=c.execute(f"SELECT {cols} FROM adb_vector_configs").fetchall()
+ else: rows=c.execute(f"SELECT {cols} FROM adb_vector_configs WHERE user_id=?",(u["id"],)).fetchall()
return [dict(r) for r in rows]
@app.post("/api/adb/test/{vid}")
@@ -734,14 +881,7 @@ async def test_adb(vid: str, u=Depends(require("admin","user"))):
cfg = c.execute("SELECT * FROM adb_vector_configs WHERE id=?",(vid,)).fetchone()
if not cfg: raise HTTPException(404)
try:
- import oracledb
- params = {"user": cfg["username"], "password": _dec(cfg["password_enc"]), "dsn": cfg["dsn"]}
- if cfg["use_mtls"] and cfg.get("wallet_dir"):
- params["config_dir"] = cfg["wallet_dir"]
- params["wallet_location"] = cfg["wallet_dir"]
- if cfg.get("wallet_password_enc"):
- params["wallet_password"] = _dec(cfg["wallet_password_enc"])
- conn = oracledb.connect(**params)
+ conn = _get_adb_connection(dict(cfg))
cur = conn.cursor(); cur.execute("SELECT 1 FROM DUAL"); cur.close(); conn.close()
return {"status":"success","message":"Conexão Autonomous DB OK!"}
except ImportError:
@@ -756,6 +896,63 @@ async def del_adb(vid: str, u=Depends(require("admin","user"))):
if d.exists(): shutil.rmtree(d)
return {"ok": True}
+@app.post("/api/adb/{vid}/ensure-table")
+async def ensure_table(vid: str, u=Depends(require("admin","user"))):
+ with db() as c:
+ cfg = c.execute("SELECT * FROM adb_vector_configs WHERE id=?", (vid,)).fetchone()
+ if not cfg: raise HTTPException(404)
+ try:
+ _ensure_embeddings_table(dict(cfg))
+ return {"ok": True, "table": cfg["table_name"]}
+ except Exception as e:
+ raise HTTPException(500, f"Falha ao criar tabela: {str(e)[:500]}")
+
+@app.post("/api/adb/{vid}/ingest")
+async def ingest_documents(vid: str, req: IngestDocReq, bg: BackgroundTasks, u=Depends(require("admin","user"))):
+ with db() as c:
+ cfg = c.execute("SELECT * FROM adb_vector_configs WHERE id=?", (vid,)).fetchone()
+ if not cfg: raise HTTPException(404, "ADB config not found")
+ cfg = dict(cfg)
+ if not cfg.get("genai_config_id"):
+ raise HTTPException(400, "GenAI config not linked to this ADB connection")
+ with db() as c:
+ gc = c.execute("SELECT * FROM genai_configs WHERE id=?", (cfg["genai_config_id"],)).fetchone()
+ if not gc: raise HTTPException(400, "Linked GenAI config not found")
+ bg.add_task(_ingest_documents_task, cfg, dict(gc), req.documents, u["id"], u["username"])
+ return {"ok": True, "message": f"Ingestão iniciada para {len(req.documents)} documentos", "config_id": vid}
+
+def _ingest_documents_task(cfg: dict, genai_cfg: dict, documents: list, user_id: str, username: str):
+ """Background task: embed and insert documents into ADB via OCI GenAI."""
+ import array
+ emb_model = cfg.get("embedding_model_id", "cohere.embed-multilingual-v3.0")
+ table_name = cfg.get("table_name", "CIS_EMBEDDINGS")
+ _ensure_embeddings_table(cfg)
+ conn = _get_adb_connection(cfg)
+ try:
+ cur = conn.cursor()
+ inserted = 0
+ for doc in documents:
+ try:
+ content = doc.get("content", "")
+ if not content: continue
+ embedding = _embed_text(content, genai_cfg, emb_model)
+ vec = array.array('d', embedding)
+ cur.execute(f"""
+ INSERT INTO {table_name} (ID, CONTENT, EMBEDDING, METADATA, SOURCE)
+ VALUES (:1, :2, :3, :4, :5)
+ """, [str(uuid.uuid4()), content, vec, doc.get("metadata", ""), doc.get("source", "manual_upload")])
+ inserted += 1
+ except Exception as e:
+ log.error(f"Failed to ingest document: {e}")
+ conn.commit()
+ cur.close()
+ log.info(f"Ingested {inserted}/{len(documents)} documents into {table_name}")
+ _audit(user_id, username, "ingest_documents", cfg["id"], f"{inserted} documents")
+ except Exception as e:
+ log.error(f"Ingestion task failed: {e}")
+ finally:
+ conn.close()
+
# ── Reports ───────────────────────────────────────────────────────────────────
@app.post("/api/reports/run")
async def run_report(req: RunReportReq, bg: BackgroundTasks, u=Depends(require("admin","user"))):
@@ -852,7 +1049,26 @@ async def chat(msg: ChatMsg, u=Depends(current_user)):
with db() as c:
prev = c.execute("SELECT role,content FROM chat_messages WHERE session_id=? AND role IN ('user','assistant') ORDER BY created_at ASC", (sid,)).fetchall()
history = [{"role":r["role"],"content":r["content"]} for r in prev]
- resp = _call_genai(dict(genai_cfg), msg.message, history[:-1] if len(history) > 1 else None)
+
+ # ── RAG: augment with vector context if ADB config is active ──
+ rag_context = ""
+ adb_cfg = _get_active_adb_config(u["id"])
+ if adb_cfg:
+ try:
+ with db() as c:
+ emb_genai = c.execute("SELECT * FROM genai_configs WHERE id=?", (adb_cfg["genai_config_id"],)).fetchone()
+ if emb_genai:
+ emb_model = adb_cfg.get("embedding_model_id", "cohere.embed-multilingual-v3.0")
+ query_embedding = _embed_text(msg.message, dict(emb_genai), emb_model)
+ documents = _vector_search(adb_cfg, query_embedding, top_k=5)
+ if documents:
+ rag_context = _build_rag_context(documents)
+ log.info(f"RAG: Retrieved {len(documents)} documents for query")
+ except Exception as e:
+ log.warning(f"RAG retrieval failed (non-fatal): {e}")
+
+ augmented_message = RAG_SYSTEM_PROMPT.format(context=rag_context, question=msg.message) if rag_context else msg.message
+ resp = _call_genai(dict(genai_cfg), augmented_message, history[:-1] if len(history) > 1 else None)
except Exception as e:
resp = f"❌ Erro GenAI: {str(e)[:400]}"
else:
diff --git a/frontend/index.html b/frontend/index.html
index 2b325c1..910f93c 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -194,7 +194,7 @@ const V='1.1';
const LOGO_W=``;
const LOGO_R=``;
-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`
${LOGO_W} OCI CIS Agent
Infrastructure & Security · v${V}
Principal
@@ -244,7 +244,7 @@ ${tabs.map(t=>`
${t[1]}${t[2]}
`).join('')}
${S.user?.role==='admin'?`
Administração
${atabs.map(t=>`
${t[1]}${t[2]}
`).join('')}`:''}
-
${i}
${S.user?.username}
${S.user?.role}
+
${i}
${S.user?.first_name?S.user.first_name+' '+S.user.last_name:S.user?.username}
${S.user?.role}
⏻
`}
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`
`}).join('');
return`
+${S.adbCfg.some(c=>c.genai_config_id&&c.is_active)?'
RAG Ativo':''}
${ms}
@@ -442,11 +443,12 @@ async function lnkMcp(){const mid=document.getElementById('mup').value;const aid
/* ── ADB Vector ── */
function rADB(){return`
🗄️ Conexões Registradas
-
| Nome | DSN | User | Tabela | mTLS | Wallet | Ações |
-${S.adbCfg.map(c=>`| ${c.config_name} | ${c.dsn} |
+| Nome | DSN | User | Tabela | mTLS | Wallet | Embed | Ações |
+${S.adbCfg.map(c=>{const em=S.embModels[c.embedding_model_id];return`| ${c.config_name} | ${c.dsn} |
${c.username} | ${c.table_name} |
${c.use_mtls?'✅':'—'} | ${c.wallet_dir?'✅':'❌'} |
- |
`).join('')}
+${c.genai_config_id?''+(em?em.name:c.embedding_model_id)+'':'—'} |
+ |
`}).join('')}
${!S.adbCfg.length?'
Nenhuma conexão ADB registrada.
':''}
➕ Nova Conexão
Conexão via python-oracledb Thin mode com Wallet (mTLS) para Oracle Autonomous Database.
@@ -455,40 +457,115 @@ ${!S.adbCfg.length?'
+
+
Credenciais OCI usadas para gerar embeddings via GenAI.
+
+
Modelo OCI GenAI para gerar vetores.
+
📤 Upload Wallet
-
`}
+
+
📄 Ingerir Documentos
+
Gere embeddings e insira documentos na tabela ADB para uso com RAG.
+
+
+
+
Array de objetos: [{"content":"texto","source":"fonte","metadata":"info"}]
+
+
`}
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`
👥 Usuários
-
| Usuário | Email | Role | MFA | Status | Último Login | Ações |
-${S.users.map(u=>`| ${u.username} | ${u.email||'—'} |
-${u.role} | ${u.mfa_enabled?'✅':'❌'} |
-${u.is_active?'Ativo':'Inativo'} |
+function rUsers(){return`👥 Gerenciamento de Usuários
+
| Nome | Usuário | Email | Role | MFA | Status | Último Login | Ações |
+${S.users.map(u=>`
+| ${u.first_name||''} ${u.last_name||''} |
+${u.username} |
+${u.email||'—'} |
+${u.role} |
+${u.mfa_enabled?'✓':'—'} |
+${u.is_active?'Ativo':'Inativo'} |
${u.last_login||'Nunca'} |
-
-${u.is_active?` `:''} |
`).join('')}
-➕ Criar Usuário
-
-
+
+${u.username!=='admin'&&u.is_active?` `:''} |
`).join('')}
+
+
➕ Criar Novo Usuário
+
+
`}
-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()}