feat: add TF reference refresh button to Terraform Agent menu
- 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
This commit is contained in:
@@ -357,14 +357,14 @@ Allow group <group-name> to read buckets in compartment <compartment-name>
|
||||
```
|
||||
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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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}
|
||||
<div style="flex:1"></div>
|
||||
<div style="position:relative">
|
||||
<div class="tf-file-btn" onclick="tfToggleMenu()" title="Menu" style="width:24px;height:24px;cursor:pointer"><svg viewBox="0 0 16 16" width="14" height="14" fill="var(--t3)"><path d="M8 4.5a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm0 5a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm0 5a1.5 1.5 0 110-3 1.5 1.5 0 010 3z"/></svg></div>
|
||||
<div class="tf-menu" id="tfMenu" style="display:none">
|
||||
<div class="tf-menu-item" onclick="tfRefreshRef()">
|
||||
<svg viewBox="0 0 16 16" width="13" height="13" fill="currentColor"><path d="M8 1a7 7 0 106.33 4h-2.1A5 5 0 108 13a5 5 0 004.24-2.36l1.52 1.02A7 7 0 018 1z"/><path d="M11 1v4h4" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
|
||||
<span>Atualizar Referência TF</span>
|
||||
<span class="tf-menu-sub" id="tfRefStatus">${S.tfRefStatus||''}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tf-file-btn" onclick="newTfChat()" title="Nova conversa" style="width:24px;height:24px"><svg viewBox="0 0 16 16" width="12" height="12" fill="var(--t3)"><path d="M8 2v12M2 8h12" stroke="var(--t3)" stroke-width="2" stroke-linecap="round"/></svg></div>
|
||||
</div>
|
||||
<div class="tf-msgs" id="tfchm">${ms}</div>
|
||||
|
||||
Reference in New Issue
Block a user