From 805286c05f88b248226574149da9db125e7b3ff8 Mon Sep 17 00:00:00 2001 From: nogueiraguh Date: Sun, 8 Mar 2026 01:05:18 -0300 Subject: [PATCH] feat: add TF reference refresh button to Terraform Agent menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add dropdown menu (⋮) in Terraform toolbar with "Atualizar Referência TF" button - Remove standalone reference-status API endpoint, keep only refresh-reference - Button triggers `terraform providers schema -json` regeneration with progress feedback - Shows line count and update timestamp after successful refresh - Update README with new line counts --- README.md | 7 +++---- backend/app.py | 16 +++++----------- frontend/index.html | 44 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 79d6661..cbe64c2 100644 --- a/README.md +++ b/README.md @@ -357,14 +357,14 @@ Allow group to read buckets in compartment ``` oci-cis-agent/ ├── backend/ -│ ├── app.py # FastAPI application (~4200 lines) +│ ├── app.py # FastAPI application (~4195 lines) │ ├── cis_reports.py # Oracle CIS Benchmark checker (6660 lines, report engine) │ ├── mcp_cis_server.py # MCP server with 12 granular CIS tools (~700 lines) │ ├── gen_tf_reference.py # OCI Terraform provider resource catalog generator │ ├── Dockerfile # Python 3.12 + OCI CLI + Terraform CLI │ └── requirements.txt # Dependencies ├── frontend/ -│ └── index.html # SPA with Oracle Cloud theme (~1980 lines) +│ └── index.html # SPA with Oracle Cloud theme (~2020 lines) ├── nginx/ │ └── default.conf # Reverse proxy config ├── docker-compose.yml # Orchestration @@ -516,8 +516,7 @@ oci-cis-agent/ | GET | `/api/terraform/workspaces/{wid}/download` | Download workspace `.tf` files | | POST | `/api/terraform/workspaces/{wid}/cancel` | Cancel running Terraform operation | | DELETE | `/api/terraform/workspaces/{wid}` | Delete workspace | -| POST | `/api/terraform/refresh-reference` | Regenerate OCI Terraform resource reference | -| GET | `/api/terraform/reference-status` | Check resource reference availability | +| POST | `/api/terraform/refresh-reference` | Regenerate OCI Terraform resource reference (UI button) | ### Chat & Reports diff --git a/backend/app.py b/backend/app.py index 4ecb88e..b979e7f 100644 --- a/backend/app.py +++ b/backend/app.py @@ -3765,22 +3765,16 @@ async def tf_delete_workspace(wid: str, u=Depends(current_user)): @app.post("/api/terraform/refresh-reference") async def tf_refresh_reference(u=Depends(current_user)): - """Regenerate the OCI Terraform resource reference from provider schema.""" + """Regenerate the OCI Terraform resource reference from provider schema (internal, triggered by UI button).""" loop = asyncio.get_event_loop() result = await loop.run_in_executor(_chat_executor, _regenerate_tf_reference) - return result - - -@app.get("/api/terraform/reference-status") -async def tf_reference_status(u=Depends(current_user)): - """Check if OCI Terraform resource reference is available.""" + # Add status info p = Path(_TF_RESOURCE_REF_PATH) if p.exists(): content = _load_tf_resource_reference() - lines = content.count('\n') if content else 0 - mtime = datetime.fromtimestamp(p.stat().st_mtime).isoformat() - return {"available": True, "lines": lines, "updated_at": mtime} - return {"available": False} + result["lines"] = content.count('\n') if content else 0 + result["updated_at"] = datetime.fromtimestamp(p.stat().st_mtime).strftime('%d/%m/%Y %H:%M') + return result def _write_tf_files(wdir: Path, tf_code: str): diff --git a/frontend/index.html b/frontend/index.html index d782531..7fd35e9 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -320,6 +320,12 @@ tbody tr:last-child td{border-bottom:none} .tf-badge-run{background:var(--bll);color:var(--bl)} .tf-badge-err{background:var(--rdl);color:var(--rd)} .tf-badge-dest{background:var(--t4);color:var(--bg)} +.tf-menu{position:absolute;top:100%;right:0;background:var(--bg);border:1px solid var(--bd);border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.12);min-width:220px;z-index:50;padding:.3rem 0;margin-top:4px} +.tf-menu-item{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;font-size:.72rem;color:var(--t1);cursor:pointer;transition:background .12s} +.tf-menu-item:hover{background:var(--bg2)} +.tf-menu-item svg{flex-shrink:0;color:var(--t3)} +.tf-menu-sub{font-size:.6rem;color:var(--t3);margin-left:auto;white-space:nowrap} +.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 h3{margin:0 0 .5rem;color:var(--rd)} @@ -346,7 +352,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}; + tfComps:[],tfCompLoading:false,tfFiles:[],tfBtab:'files',tfEditIdx:-1,tfResources:null,tfResLoading:false,tfRefStatus:''}; const API='/api'; async function $api(p,o={}){const h={...(o.headers||{})};if(S.token)h['Authorization']='Bearer '+S.token; @@ -600,6 +606,32 @@ async function clrChat(){if(S.sid)try{await $api('/chat/'+S.sid,{method:'DELETE' function newChat(){S.msgs=[];S.sid=null;S.chatFiles=[];R()} function newTfChat(){S.tfMsgs=[];S.tfSid=null;S.tfPlan=[];S.tfCode='';S.tfFiles=[];S.tfWs=null;S.tfPlanOut='';S.tfApplyOut='';S.tfDestroyOut='';S.tfStatus='draft';S.tfRunning=false;S.tfConfirmDest=false;S.tfEditIdx=-1;R()} +function tfToggleMenu(){ + const m=document.getElementById('tfMenu'); + if(!m)return; + const show=m.style.display==='none'; + 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)} +} +async function tfRefreshRef(){ + const item=document.querySelector('.tf-menu-item'); + const sub=document.getElementById('tfRefStatus'); + if(item)item.classList.add('loading'); + if(sub)sub.textContent='Atualizando...'; + try{ + const r=await $api('/terraform/refresh-reference',{method:'POST'}); + if(r.ok){ + S.tfRefStatus=r.lines+' linhas — '+r.updated_at; + if(sub)sub.textContent=S.tfRefStatus; + alert('Referência Terraform atualizada com sucesso!\n'+r.lines+' linhas de recursos OCI.'); + }else{ + alert('Erro ao atualizar: '+(r.error||'Erro desconhecido')); + if(sub)sub.textContent='Erro'; + } + }catch(e){alert('Erro: '+e.message);if(sub)sub.textContent='Erro'} + finally{if(item)item.classList.remove('loading');const m=document.getElementById('tfMenu');if(m)m.style.display='none'} +} + async function loadHistory(type){ try{const h=await $api('/chat/sessions?agent_type='+type+'&limit=50'); if(type==='chat')S.chatHistory=h;else S.tfHistory=h} @@ -758,6 +790,16 @@ function rTerraform(){ ${ociSel}${compSel} ${stBadge}
+
+
+ +
${ms}