diff --git a/backend/app.py b/backend/app.py
index 948e8b6..6823871 100644
--- a/backend/app.py
+++ b/backend/app.py
@@ -874,7 +874,7 @@ async def explore_databases(cid: str, compartment_id: str = Query(None), region:
adbs = db_client.list_autonomous_databases(comp).data
return [{"id":a.id,"display_name":a.display_name,"db_name":a.db_name,"lifecycle_state":a.lifecycle_state,
"cpu_core_count":a.cpu_core_count,"data_storage_size_in_tbs":a.data_storage_size_in_tbs,
- "is_free_tier":a.is_free_tier} for a in adbs]
+ "is_free_tier":a.is_free_tier,"whitelisted_ips":a.whitelisted_ips or []} for a in adbs]
except Exception as e:
return {"error": str(e)[:500]}
@@ -3646,6 +3646,43 @@ async def oci_adb_action(adb_id: str, req: dict, u=Depends(current_user)):
raise HTTPException(500, str(e)[:500])
+@app.post("/api/oci/autonomous-databases/{adb_id}/update-network-access")
+async def oci_adb_update_network(adb_id: str, req: dict, u=Depends(current_user)):
+ oci_config_id = req.get("oci_config_id", "")
+ ip = req.get("ip", "")
+ region = req.get("region")
+ if not oci_config_id:
+ raise HTTPException(400, "oci_config_id obrigatório")
+ if not ip:
+ raise HTTPException(400, "ip obrigatório")
+ try:
+ import oci
+ config = _get_oci_config(oci_config_id)
+ if region:
+ config["region"] = region
+ db_client = oci.database.DatabaseClient(config)
+ adb = db_client.get_autonomous_database(adb_id).data
+ current_acl = adb.whitelisted_ips or []
+ # Build new ACL: keep existing entries, add new IP if not present
+ ip_cidr = ip if "/" in ip else ip + "/32"
+ new_acl = list(set(current_acl) | {ip_cidr})
+ db_client.update_autonomous_database(
+ adb_id,
+ oci.database.models.UpdateAutonomousDatabaseDetails(whitelisted_ips=new_acl))
+ _audit(u["id"], u["username"], "adb_update_network", adb_id, f"ip={ip_cidr}")
+ return {"ok": True, "adb_id": adb_id, "ip_added": ip_cidr, "acl": new_acl}
+ except Exception as e:
+ raise HTTPException(500, str(e)[:500])
+
+
+@app.get("/api/oci/my-ip")
+async def get_my_ip(request: Request):
+ """Return the caller's public IP address."""
+ forwarded = request.headers.get("x-forwarded-for", "")
+ ip = forwarded.split(",")[0].strip() if forwarded else (request.client.host if request.client else "")
+ return {"ip": ip}
+
+
@app.get("/api/terraform/resources")
async def tf_list_resources(oci_config_id: str = Query(...), compartment_id: str = Query(...), region: str = Query(None), u=Depends(current_user)):
"""List existing OCI resources in a compartment for terraform context."""
@@ -3720,7 +3757,7 @@ async def tf_run_plan(wid: str, bg: BackgroundTasks, u=Depends(current_user)):
if ws["status"] in ("planning", "applying", "destroying"):
raise HTTPException(400, f"Workspace is {ws['status']}")
with db() as c:
- c.execute("UPDATE terraform_workspaces SET status='planning', plan_output='', error=NULL, updated_at=datetime('now') WHERE id=?", (wid,))
+ c.execute("UPDATE terraform_workspaces SET status='planning', plan_output='', apply_output='', destroy_output='', error=NULL, updated_at=datetime('now') WHERE id=?", (wid,))
bg.add_task(_terraform_exec, wid, "plan", dict(u))
return {"status": "planning"}
@@ -4178,16 +4215,18 @@ provider "oci" {{
_running_terraform.pop(wid, None)
if proc.returncode == 0:
- _update_output(f"\n✅ terraform {action} completed successfully")
+ output_lines.append(f"\n✅ terraform {action} completed successfully")
+ full_output = "\n".join(output_lines)
with db() as c:
- c.execute("UPDATE terraform_workspaces SET status=?, updated_at=datetime('now') WHERE id=?",
- (final_ok, wid))
+ c.execute(f"UPDATE terraform_workspaces SET {status_col}=?, status=?, updated_at=datetime('now') WHERE id=?",
+ (full_output, final_ok, wid))
_audit(user["id"], user["username"], f"terraform_{action}", wid, f"status={final_ok}")
else:
- _update_output(f"\n❌ terraform {action} failed (exit {proc.returncode})")
+ output_lines.append(f"\n❌ terraform {action} failed (exit {proc.returncode})")
+ full_output = "\n".join(output_lines)
with db() as c:
- c.execute("UPDATE terraform_workspaces SET status='failed', error=?, updated_at=datetime('now') WHERE id=?",
- (f"terraform {action} failed (exit {proc.returncode})", wid))
+ c.execute(f"UPDATE terraform_workspaces SET {status_col}=?, status='failed', error=?, updated_at=datetime('now') WHERE id=?",
+ (full_output, f"terraform {action} failed (exit {proc.returncode})", wid))
except Exception as e:
log.error(f"Terraform exec error: {e}")
@@ -4376,10 +4415,15 @@ async def startup():
orphaned = c.execute("UPDATE reports SET status='failed', error_msg='Interrompido: container reiniciado' WHERE status='running'").rowcount
if orphaned:
log.warning(f"Marked {orphaned} orphaned running report(s) as failed")
- # Auto-register CIS Compliance Scanner MCP server if not present
+ # Auto-register CIS Compliance Scanner MCP server if not present (deduplicate on startup)
with db() as c:
- existing = c.execute("SELECT id FROM mcp_servers WHERE name='CIS Compliance Scanner'").fetchone()
- if not existing:
+ dupes = c.execute("SELECT id FROM mcp_servers WHERE name='CIS Compliance Scanner' ORDER BY created_at ASC").fetchall()
+ if len(dupes) > 1:
+ keep = dupes[0]["id"]
+ for d in dupes[1:]:
+ c.execute("DELETE FROM mcp_servers WHERE id=?", (d["id"],))
+ log.info(f"Removed {len(dupes)-1} duplicate CIS Compliance Scanner entries, kept {keep}")
+ if not dupes:
mid = str(uuid.uuid4())
c.execute(
"INSERT INTO mcp_servers (id, user_id, name, description, server_type, command, args) VALUES (?,?,?,?,?,?,?)",
diff --git a/frontend/index.html b/frontend/index.html
index d0d28fd..eecb398 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -328,6 +328,12 @@ tbody tr:last-child td{border-bottom:none}
.tf-menu-item.loading{opacity:.6;pointer-events:none}
.tf-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:100}
.tf-modal{background:var(--bg);border-radius:var(--r);padding:1.5rem;max-width:420px;width:90%;box-shadow:0 8px 30px rgba(0,0,0,.25)}
+.tf-modal.wide{max-width:720px}
+.tf-prompt-ta{width:100%;min-height:320px;max-height:60vh;background:var(--bg2);color:var(--t1);border:1px solid var(--bd);border-radius:6px;padding:.6rem;font-family:monospace;font-size:.72rem;line-height:1.5;resize:vertical;box-sizing:border-box}
+.tf-prompt-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:.6rem}
+.tf-prompt-header h3{margin:0;color:var(--ac);font-size:.82rem}
+.tf-prompt-saved{font-size:.62rem;color:var(--t3);transition:opacity .3s}
+.tf-prompt-name{background:var(--bg2);color:var(--t1);border:1px solid var(--bd);border-radius:4px;padding:.25rem .4rem;font-size:.72rem;width:100%;margin-bottom:.5rem;box-sizing:border-box}
.tf-modal h3{margin:0 0 .5rem;color:var(--rd)}
.tf-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--t3);font-size:.76rem;gap:.3rem}
.tf-empty svg{width:40px;height:40px;fill:#7b42bc;opacity:.3}
@@ -343,7 +349,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:[],models:{},regions:[],embModels:{},expData:null,expCfg:'',expSelRegions:[],expRegDdOpen:false,expTree:[],expSelComp:'',expCat:'Compute',expResType:'instances',expTreeOpen:{},expLoading:false,expRegions:[],expCounts:{},editing:null,
+const S={user:null,token:null,tab:'chat',msgs:[],sid:null,reports:[],ociCfg:[],genaiCfg:[],adbCfg:[],mcpSvr:[],users:[],models:{},regions:[],embModels:{},expData:null,expCfg:'',expSelRegions:[],expRegDdOpen:false,expTree:[],expSelComp:'',expCat:'Compute',expResType:'instances',expTreeOpen:{},expLoading:false,expRegions:[],expCounts:{},expTreeW:null,editing:null,
chatModel:'',chatOci:'',chatRegion:'',chatCompartment:'',chatHistOpen:false,chatHistory:[],tfHistOpen:false,tfHistory:[],
chatParams:{temperature:1,max_tokens:6000,top_p:0.95,top_k:1,frequency_penalty:0,presence_penalty:0},chatPanel:'',chatUseTools:true,
chatPrompts:[],editingPrompt:null,trackingReportId:null,ociRegions:{},rptSelRegions:[],rptRegionsOpen:false,rptRegionFilter:'',
@@ -352,7 +358,7 @@ const S={user:null,token:null,tab:'chat',msgs:[],sid:null,reports:[],ociCfg:[],g
cisVer:null,cisCheckResult:null,cisUpdating:false,rptHistFilter:'',chatFiles:[],
tfMsgs:[],tfSid:null,tfModel:'',tfOci:'',tfRegion:'',tfCompartment:'',tfPlan:[],tfCode:'',tfPanel:'',
tfWs:null,tfWsList:[],tfPlanOut:'',tfApplyOut:'',tfDestroyOut:'',tfStatus:'draft',tfRunning:false,tfConfirmDest:false,tfMdOpen:false,
- tfComps:[],tfCompLoading:false,tfFiles:[],tfBtab:'files',tfEditIdx:-1,tfResources:null,tfResLoading:false,tfRefStatus:''};
+ tfComps:[],tfCompLoading:false,tfFiles:[],tfBtab:'files',tfEditIdx:-1,tfResources:null,tfResLoading:false,tfRefStatus:'',tfBottomH:null};
const API='/api';
async function $api(p,o={}){const h={...(o.headers||{})};if(S.token)h['Authorization']='Bearer '+S.token;
@@ -404,6 +410,8 @@ function R(){try{
// Restore scroll positions
requestAnimationFrame(()=>{
Object.entries(scrollMap).forEach(([id,top])=>{if(id==='_bcLeft'){const el=document.querySelector('.tf-bc-left');if(el)el.scrollTop=top}else{const el=document.getElementById(id);if(el)el.scrollTop=top}});
+ if(S.tfBottomH){const bel=document.getElementById('tfBottom');if(bel){bel.style.height=S.tfBottomH+'px';bel.style.flex='none'}}
+ if(S.expTreeW){const tel=document.getElementById('exp-tree');if(tel)tel.style.width=S.expTreeW+'px'}
});
if(S.editing?.type==='mcp')setTimeout(mcpTypeFields,0)
}catch(e){console.error('Render error:',e);document.getElementById('app').innerHTML='
Render Error '+e.message+' '}}
@@ -613,6 +621,33 @@ function tfToggleMenu(){
m.style.display=show?'block':'none';
if(show){const h=e=>{if(!m.contains(e.target)&&!e.target.closest('[onclick*="tfToggleMenu"]')){m.style.display='none';document.removeEventListener('click',h)}};setTimeout(()=>document.addEventListener('click',h),0)}
}
+let _tfPromptSaveTimer=null;
+async function tfOpenPrompt(){
+ document.getElementById('tfMenu').style.display='none';
+ try{
+ const prompts=await $api('/prompts/terraform');
+ const active=prompts.find(p=>p.is_active)||prompts[0];
+ if(!active){alert('Nenhum prompt encontrado.');return}
+ S.tfPrompt={id:active.id,name:active.name,content:active.content};
+ S.tfPromptOpen=true;R();
+ }catch(e){alert('Erro ao carregar prompt: '+e.message)}
+}
+function tfPromptChanged(field,value){
+ if(!S.tfPrompt)return;
+ S.tfPrompt[field]=value;
+ const el=document.getElementById('tfPromptSaved');
+ if(el){el.textContent='Salvando...';el.style.opacity='1'}
+ clearTimeout(_tfPromptSaveTimer);
+ _tfPromptSaveTimer=setTimeout(async()=>{
+ try{
+ await $api('/prompts/'+S.tfPrompt.id,{method:'PUT',body:{name:S.tfPrompt.name,content:S.tfPrompt.content,is_active:true}});
+ const el=document.getElementById('tfPromptSaved');
+ if(el){el.textContent='Salvo ✓';el.style.opacity='1';setTimeout(()=>{if(el)el.style.opacity='.4'},2000)}
+ }catch(e){const el=document.getElementById('tfPromptSaved');if(el){el.textContent='Erro ao salvar';el.style.opacity='1'}}
+ },800);
+}
+function tfClosePrompt(){S.tfPromptOpen=false;R()}
+
async function tfRefreshRef(){
const item=document.querySelector('.tf-menu-item');
const sub=document.getElementById('tfRefStatus');
@@ -644,11 +679,44 @@ async function loadSession(sid,type){
S.sid=sid;S.msgs=d.messages.map(m=>({r:m.role,c:m.content,t:m.created_at?.slice(11,16)||''}));R();scCh();
}else{
S.tfSid=sid;S.tfMsgs=d.messages.map(m=>({r:m.role,c:m.content,t:m.created_at?.slice(11,16)||''}));
- S.tfFiles=[];S.tfCode='';S.tfPlan=[];
- const last=[...d.messages].reverse().find(m=>m.role==='assistant'&&m.content&&m.content.includes('```'));
- if(last)fmTf(last.content);
+ S.tfFiles=[];S.tfCode='';S.tfPlan=[];S.tfPlanOut='';S.tfApplyOut='';S.tfDestroyOut='';
try{const wsList=await $api('/terraform/workspaces');const ws=wsList.find(w=>w.session_id===sid);
- if(ws){S.tfWs=ws.id;S.tfStatus=ws.status}else{S.tfWs=null;S.tfStatus='draft'}
+ if(ws){
+ S.tfWs=ws.id;S.tfStatus=ws.status;
+ const detail=await $api('/terraform/workspaces/'+ws.id);
+ S.tfPlanOut=detail.plan_output||'';S.tfApplyOut=detail.apply_output||'';S.tfDestroyOut=detail.destroy_output||'';
+ if(detail.tf_code){
+ S.tfCode=detail.tf_code;
+ const parts=detail.tf_code.split(/^\/\/\s*filename:\s*/m).filter(Boolean);
+ let md='';
+ if(parts.length>1){
+ md=parts.map(p=>{const nl=p.indexOf('\n');const fn=p.substring(0,nl).trim();const code=p.substring(nl+1).trim();return '```hcl\n// filename: '+fn+'\n'+code+'\n```'}).join('\n\n');
+ }else{md='```hcl\n'+detail.tf_code+'\n```'}
+ const lastMsg=[...d.messages].reverse().find(m=>m.role==='assistant'&&m.content&&(m.content.includes('```plan')||/Resource Plan/.test(m.content)));
+ if(lastMsg){
+ const pm=lastMsg.content.match(/```plan\s*\n([\s\S]*?)```/);
+ if(pm){md+='\n\n```plan\n'+pm[1]+'```'}
+ else{
+ const fm=lastMsg.content.match(/(\*{0,2}Resource Plan\*{0,2})\s*\n((?:[+~][\s\S]*?)(?=\n\n|\n##|\n\*\*|$))/);
+ if(fm)md+='\n\n'+fm[0];
+ }
+ }
+ fmTf(md);
+ }
+ if(detail.oci_config_id){
+ S.tfOci=detail.oci_config_id;
+ const oc=S.ociCfg.find(x=>x.id===detail.oci_config_id);
+ if(oc)S.tfRegion=oc.region;
+ }
+ if(detail.compartment_id)S.tfCompartment=detail.compartment_id;
+ if(S.tfPlanOut)S.tfBtab='plan';
+ else if(S.tfFiles.length)S.tfBtab='files';
+ if(S.tfOci){tfLoadComps();if(S.tfCompartment)tfLoadResources()}
+ }else{
+ S.tfWs=null;S.tfStatus='draft';
+ const last=[...d.messages].reverse().find(m=>m.role==='assistant'&&m.content&&m.content.includes('```'));
+ if(last)fmTf(last.content);
+ }
}catch(e){S.tfWs=null;S.tfStatus='draft'}
R();
}
@@ -847,9 +915,20 @@ function fmTf(t){
r=r.replace(/```plan\s*\n([\s\S]*?)```/g,(m,plan)=>{
const lines=plan.trim().split('\n');
S.tfPlan=lines.map(l=>{const m2=l.match(/^[+~]\s+(\S+)\s+\((.+)\)$/);return m2?{type:m2[1],desc:m2[2]}:{type:l.replace(/^[+~]\s*/,'').trim(),desc:''}}).filter(x=>x.type);
- return ''+
- S.tfPlan.map(x=>'
+ '+escHtml(x.type)+' '+escHtml(x.desc)+'
').join('')+'
';
+ return _renderTfPlanBlock();
});
+ // Also match free-form "Resource Plan" / "**Resource Plan**" followed by +/~ lines (model often skips ```plan block)
+ if(!S.tfPlan.length){
+ r=r.replace(/(\*{0,2}Resource Plan\*{0,2})\s*\n((?:[+~][\s\S]*?)(?=\n\n|\n##|\n\*\*|$))/g,(m,hdr,plan)=>{
+ const lines=plan.trim().split('\n').filter(l=>/^[+~]/.test(l.trim()));
+ S.tfPlan=lines.map(l=>{const clean=l.trim().replace(/^[+~]\s*/,'').replace(/\s{2,}/g,' ').trim();return{type:clean,desc:''}}).filter(x=>x.type);
+ return _renderTfPlanBlock();
+ });
+ }
+ function _renderTfPlanBlock(){
+ return ''+
+ S.tfPlan.map(x=>'
+ '+escHtml(x.type)+' '+(x.desc?''+escHtml(x.desc)+' ':'')+'
').join('')+'
';
+ }
// Auto-split: if model returned 1-2 large blocks, split into multiple files
// Runs on BOTH first generation and corrections — monolithic responses always get split
let didAutoSplit=false;
@@ -945,6 +1024,10 @@ function rTerraform(){
${S.tfConfirmDest?rTfConfirmDestroy():''}
+ ${S.tfPromptOpen?rTfPromptModal():''}
`;
}
+function rTfPromptModal(){
+ const p=S.tfPrompt||{};
+ return``;
+}
function _tfBadge(st){
const m={draft:['draft','Draft'],planning:['run','Planning...'],planned:['ok','Plan OK'],applying:['run','Applying...'],applied:['ok','Applied'],destroying:['run','Destroying...'],destroyed:['dest','Destroyed'],failed:['err','Failed']};
@@ -1089,7 +1182,7 @@ function tfStartResize(e){
e.preventDefault();const el=document.getElementById('tfBottom');if(!el)return;
const startY=e.clientY;const startH=el.offsetHeight;
const bar=e.target;bar.classList.add('dragging');
- function onMove(ev){const dh=startY-ev.clientY;el.style.height=Math.max(100,Math.min(startH+dh,window.innerHeight-200))+'px'}
+ function onMove(ev){const dh=startY-ev.clientY;const h=Math.max(100,Math.min(startH+dh,window.innerHeight-200));el.style.height=h+'px';el.style.flex='none';S.tfBottomH=h}
function onUp(){bar.classList.remove('dragging');document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp)}
document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp);
}
@@ -1179,12 +1272,34 @@ function rTfResourcesPanel(){
async function ociInstanceAction(id,action){
const label=action==='START'?'Iniciar':'Parar';
if(!confirm(label+' esta instância?'))return;
- try{await $api('/oci/instances/'+id+'/action',{method:'POST',body:{action,oci_config_id:S.expCfg,region:null}});alert('Instância '+(action==='START'?'iniciando':'parando')+'...');expLoadRes()}catch(e){alert('Erro: '+e.message)}
+ try{await $api('/oci/instances/'+id+'/action',{method:'POST',body:{action,oci_config_id:S.expCfg,region:null}});expPollState(action==='START'?'RUNNING':'STOPPED')}catch(e){alert('Erro: '+e.message)}
}
async function ociAdbAction(id,action){
const label=action==='start'?'Iniciar':'Parar';
if(!confirm(label+' este Autonomous Database?'))return;
- try{await $api('/oci/autonomous-databases/'+id+'/action',{method:'POST',body:{action,oci_config_id:S.expCfg,region:null}});alert('Autonomous DB '+(action==='start'?'iniciando':'parando')+'...');expLoadRes()}catch(e){alert('Erro: '+e.message)}
+ try{await $api('/oci/autonomous-databases/'+id+'/action',{method:'POST',body:{action,oci_config_id:S.expCfg,region:null}});expPollState(action==='start'?'AVAILABLE':'STOPPED')}catch(e){alert('Erro: '+e.message)}
+}
+async function ociAdbUpdateNet(id){
+ try{
+ const ipResp=await $api('/oci/my-ip');
+ let ip=ipResp.ip||'';
+ ip=prompt('Adicionar IP à ACL do Autonomous Database.\nSeu IP detectado:',ip);
+ if(!ip)return;
+ await $api('/oci/autonomous-databases/'+id+'/update-network-access',{method:'POST',body:{ip,oci_config_id:S.expCfg}});
+ alert('IP '+ip+' adicionado à ACL com sucesso.');
+ expLoadRes();
+ }catch(e){alert('Erro: '+e.message)}
+}
+function expPollState(target){
+ let tries=0;const max=12;
+ const poll=async()=>{
+ await expLoadRes();
+ const items=S.expData||[];
+ const reached=items.some(i=>i.lifecycle_state===target);
+ if(reached||++tries>=max)return;
+ setTimeout(poll,5000);
+ };
+ setTimeout(poll,3000);
}
function tfSwitchBtab(tab){
S.tfBtab=tab;S.tfEditIdx=-1;
@@ -1265,7 +1380,7 @@ async function tfSaveAndPlan(){
}
// Run plan
await $api('/terraform/workspaces/'+S.tfWs+'/plan',{method:'POST'});
- S.tfStatus='planning';S.tfRunning=true;S.tfPlanOut='';S.tfBtab='plan';R();
+ S.tfStatus='planning';S.tfRunning=true;S.tfPlanOut='';S.tfApplyOut='';S.tfDestroyOut='';S.tfBtab='plan';R();
await tfPollExec();
}catch(e){alert('Erro: '+e.message)}
}
@@ -1393,7 +1508,7 @@ function expSetTab(t){S.expResType=t;S.expData=null;R();if(S.expSelComp)expLoadR
function expToggleNode(id){S.expTreeOpen[id]=!S.expTreeOpen[id];R()}
async function expSelectComp(id){S.expSelComp=id;S.expCounts={};await expLoadRes()}
function expStartResize(e){e.preventDefault();const tree=document.getElementById('exp-tree');const handle=e.target;if(!tree)return;handle.classList.add('dragging');const startX=e.clientX;const startW=tree.offsetWidth;
- const onMove=ev=>{const w=Math.max(140,Math.min(startW+(ev.clientX-startX),window.innerWidth*0.5));tree.style.width=w+'px'};
+ const onMove=ev=>{const w=Math.max(140,Math.min(startW+(ev.clientX-startX),window.innerWidth*0.5));tree.style.width=w+'px';S.expTreeW=w};
const onUp=()=>{handle.classList.remove('dragging');document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp);document.body.style.cursor='';document.body.style.userSelect=''};
document.body.style.cursor='col-resize';document.body.style.userSelect='none';document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp)}
async function expRefresh(){if(S.expCfg){S.expTree=[];S.expCounts={};R();await expLoadTree();if(S.expSelComp)await expLoadRes()}}
@@ -1437,13 +1552,14 @@ function renderExpData(d){if(!d)return'Selecione um compartm
return`
${d.length} item(ns) encontrado(s)
${d.map(i=>{
const keys=Object.keys(i);
const name=i.display_name||i.name||i.db_name||i.application||'—';
- const skip=['display_name','name','db_name','lifecycle_state','application','rules','routes'];
+ const skip=['display_name','name','db_name','lifecycle_state','application','rules','routes','whitelisted_ips'];
+ const aclHtml=isAdb&&i.whitelisted_ips&&i.whitelisted_ips.length?'
ACL: '+i.whitelisted_ips.join(', ')+' ':'';
let actionBtn='';
if(isInst&&i.lifecycle_state==='RUNNING')actionBtn='
■ Stop ';
else if(isInst&&i.lifecycle_state==='STOPPED')actionBtn='
▶ Start ';
- else if(isAdb&&i.lifecycle_state==='AVAILABLE')actionBtn='
■ Stop ';
- else if(isAdb&&i.lifecycle_state==='STOPPED')actionBtn='
▶ Start ';
- return`
${name} ${i.lifecycle_state?`
${i.lifecycle_state} `:''}${actionBtn}
+ else if(isAdb&&i.lifecycle_state==='AVAILABLE')actionBtn='
■ Stop 🔒 Update Network Access ';
+ else if(isAdb&&i.lifecycle_state==='STOPPED')actionBtn='
▶ Start 🔒 Update Network Access ';
+ return`
${name} ${i.lifecycle_state?` ${i.lifecycle_state} `:''}${actionBtn}${aclHtml}
${keys.filter(k=>!skip.includes(k)).map(k=>`${k}: ${typeof i[k]==='object'?JSON.stringify(i[k]):i[k]} `).join(' ')}
`}).join('')}
`}
function rExpSecLists(d){
return`
${d.length} security list(s)
${d.map((sl,idx)=>{