feat: robust TF file correction, move Start/Stop to Explorer, increase context
- Inject existing workspace files + plan/apply output into Terraform chat context so model corrects in-place instead of regenerating from scratch - Add "REGRA CRÍTICA: Correção de Arquivos Existentes" to system prompt requiring the model to preserve file structure and fix surgically - Increase Terraform Agent max_tokens from 16K to 32K - Remove "Nova conversa" button from Terraform toolbar - Move Start/Stop Instance and ADB buttons from Terraform Resources panel to OCI Account Explorer (instances and databases tabs) - Remove unused _ociResOid helper and old action functions
This commit is contained in:
@@ -2038,6 +2038,20 @@ Abaixo estão os nomes corretos de resource types do provider oracle/oci. O mode
|
||||
- **DRG Route Table**: `oci_core_drg_route_table`
|
||||
- **DRG Route Distribution**: `oci_core_drg_route_distribution`
|
||||
- **DRG Route Table Route Rule**: `oci_core_drg_route_table_route_rule`
|
||||
|
||||
### REGRA CRÍTICA: Correção de Arquivos Existentes
|
||||
Quando a mensagem do usuário incluir a seção "ARQUIVOS TERRAFORM ATUAIS NO WORKSPACE", significa que JÁ EXISTEM arquivos gerados anteriormente. Neste caso:
|
||||
|
||||
1. **NUNCA descarte os arquivos existentes**. Eles são o trabalho atual do usuário.
|
||||
2. **Gere TODOS os arquivos completos** — mesmo os que não precisaram de alteração. O sistema substitui todos os arquivos do workspace pelo que você gerar.
|
||||
3. **Corrija APENAS o que está errado**: se o erro é em `networking.tf`, mantenha `variables.tf`, `compute.tf`, `outputs.tf` etc. EXATAMENTE como estão e corrija apenas `networking.tf`.
|
||||
4. **Analise o output do terraform plan/apply** se incluído na mensagem — ele mostra exatamente qual recurso, atributo ou argumento causou o erro.
|
||||
5. **Mantenha a mesma estrutura de arquivos**: use os mesmos nomes de arquivo (`// filename: xxx.tf`) que já existem. NÃO renomeie, NÃO reorganize, NÃO mescle arquivos.
|
||||
6. **Preserve nomes de recursos**: NÃO renomeie recursos existentes (ex: `oci_core_vcn.hub` não deve virar `oci_core_vcn.main`), pois isso causaria destroy+recreate desnecessário.
|
||||
7. Se o erro for um resource type inválido, consulte a referência de recursos OCI neste prompt para encontrar o nome correto.
|
||||
8. Se o erro for um argumento inválido (Unsupported argument), remova-o ou substitua pela sintaxe correta.
|
||||
|
||||
**RESUMO**: Ao corrigir, regenere todos os arquivos mantendo conteúdo intacto nos que não têm erro, e corrija cirurgicamente os que têm problema. O resultado deve funcionar no `terraform plan` sem erros na primeira tentativa.
|
||||
"""
|
||||
|
||||
# ── Terraform OCI Resource Reference (generated at build time, updatable at runtime) ──
|
||||
@@ -3168,7 +3182,7 @@ async def _chat_background(mid: str, sid: str, msg: ChatMsg, user: dict, genai_c
|
||||
|
||||
# ── Terraform agent: boost max_tokens for complex infra code ──
|
||||
if agent_type == "terraform":
|
||||
cfg_dict["max_tokens"] = max(int(cfg_dict.get("max_tokens", 6000)), 16000)
|
||||
cfg_dict["max_tokens"] = max(int(cfg_dict.get("max_tokens", 6000)), 32000)
|
||||
|
||||
# ── Inject existing OCI resources for terraform agent ──
|
||||
if agent_type == "terraform" and active_oci_id:
|
||||
|
||||
@@ -800,7 +800,6 @@ function rTerraform(){
|
||||
</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>
|
||||
<div class="tf-input">
|
||||
@@ -997,12 +996,12 @@ function rTfResourcesPanel(){
|
||||
const cats=[
|
||||
{key:'vcns',label:'VCNs',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M1 3h14v2H1zm2 4h10v2H3zm3 4h4v2H6z"/></svg>',fmt:r=>`${r.display_name} <span style="opacity:.6">(${(r.cidr_blocks||[]).join(', ')})</span>`},
|
||||
{key:'subnets',label:'Subnets',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M2 2h5v5H2zm7 0h5v5H9zm-7 7h5v5H2zm7 0h5v5H9z"/></svg>',fmt:r=>`${r.display_name} <span style="opacity:.6">(${r.cidr_block}, ${r.public?'pública':'privada'})</span>`},
|
||||
{key:'instances',label:'Compute',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M2 2h12v12H2z"/></svg>',fmt:r=>{const stopped=r.state==='STOPPED';const running=r.state==='RUNNING';const stC=stopped?'var(--rd)':running?'var(--gn)':'var(--t3)';return`${r.display_name} <span style="opacity:.6">(${r.shape})</span> <span style="color:${stC};font-weight:600;font-size:.62rem">${r.state}</span>${stopped?` <button class="btn bsm" style="font-size:.58rem;padding:1px 6px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:4px" onclick="event.stopPropagation();ociStartInstance('${r.id}')">▶ Start</button>`:running?` <button class="btn bsm" style="font-size:.58rem;padding:1px 6px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:4px" onclick="event.stopPropagation();ociStopInstance('${r.id}')">■ Stop</button>`:''}`}},
|
||||
{key:'instances',label:'Compute',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M2 2h12v12H2z"/></svg>',fmt:r=>{const stopped=r.state==='STOPPED';const running=r.state==='RUNNING';const stC=stopped?'var(--rd)':running?'var(--gn)':'var(--t3)';return`${r.display_name} <span style="opacity:.6">(${r.shape})</span> <span style="color:${stC};font-weight:600;font-size:.62rem">${r.state}</span>`}},
|
||||
{key:'internet_gateways',label:'Internet GWs',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><circle cx="8" cy="8" r="6" fill="none" stroke="#7b42bc" stroke-width="1.5"/><path d="M1 8h14M8 2c-2 2-2 10 0 12m0-12c2 2 2 10 0 12"/></svg>',fmt:r=>r.display_name},
|
||||
{key:'nat_gateways',label:'NAT GWs',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M3 3l5 5-5 5"/><path d="M8 8h6" stroke="#7b42bc" stroke-width="1.5"/></svg>',fmt:r=>r.display_name},
|
||||
{key:'route_tables',label:'Route Tables',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M2 3h12v2H2zm0 4h8v2H2zm0 4h10v2H2z"/></svg>',fmt:r=>r.display_name},
|
||||
{key:'security_lists',label:'Security Lists',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M8 1l6 3v4c0 3.5-2.5 6.5-6 7.5C4.5 14.5 2 11.5 2 8V4z"/></svg>',fmt:r=>r.display_name},
|
||||
{key:'autonomous_databases',label:'Databases',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><ellipse cx="8" cy="4" rx="6" ry="2.5"/><path d="M2 4v8c0 1.4 2.7 2.5 6 2.5s6-1.1 6-2.5V4"/></svg>',fmt:r=>{const stopped=r.state==='STOPPED';const avail=r.state==='AVAILABLE';const stC=stopped?'var(--rd)':avail?'var(--gn)':'var(--t3)';return`${r.display_name} <span style="opacity:.6">(${r.db_name}${r.is_free_tier?' Free':''})</span> <span style="color:${stC};font-weight:600;font-size:.62rem">${r.state}</span>${stopped?` <button class="btn bsm" style="font-size:.58rem;padding:1px 6px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:4px" onclick="event.stopPropagation();ociStartAdb('${r.id}')">▶ Start</button>`:avail?` <button class="btn bsm" style="font-size:.58rem;padding:1px 6px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:4px" onclick="event.stopPropagation();ociStopAdb('${r.id}')">■ Stop</button>`:''}`}},
|
||||
{key:'autonomous_databases',label:'Databases',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><ellipse cx="8" cy="4" rx="6" ry="2.5"/><path d="M2 4v8c0 1.4 2.7 2.5 6 2.5s6-1.1 6-2.5V4"/></svg>',fmt:r=>{const stopped=r.state==='STOPPED';const avail=r.state==='AVAILABLE';const stC=stopped?'var(--rd)':avail?'var(--gn)':'var(--t3)';return`${r.display_name} <span style="opacity:.6">(${r.db_name}${r.is_free_tier?' Free':''})</span> <span style="color:${stC};font-weight:600;font-size:.62rem">${r.state}</span>`}},
|
||||
{key:'block_volumes',label:'Block Volumes',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M3 2h10v12H3z"/></svg>',fmt:r=>`${r.display_name} <span style="opacity:.6">(${r.size_in_gbs||'?'} GB)</span>`},
|
||||
{key:'load_balancers',label:'Load Balancers',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M8 2v12M2 8h12M4 4l8 8M12 4l-8 8"/></svg>',fmt:r=>`${r.display_name} <span style="opacity:.6">(${r.shape_name||'flexible'})</span>`}
|
||||
];
|
||||
@@ -1025,11 +1024,16 @@ function rTfResourcesPanel(){
|
||||
html+='</div>';
|
||||
return html;
|
||||
}
|
||||
function _ociResOid(){return S.tfOci||(S.tfModel.startsWith('cfg:')?((S.genaiCfg.find(x=>x.id===S.tfModel.slice(4))||{}).oci_config_id||''):'')}
|
||||
async function ociStartInstance(id){if(!confirm('Iniciar esta instância?'))return;try{await $api('/oci/instances/'+id+'/action',{method:'POST',body:{action:'START',oci_config_id:_ociResOid(),region:S.tfRegion||null}});alert('Instância iniciando...');tfLoadResources()}catch(e){alert('Erro: '+e.message)}}
|
||||
async function ociStopInstance(id){if(!confirm('Parar esta instância?'))return;try{await $api('/oci/instances/'+id+'/action',{method:'POST',body:{action:'STOP',oci_config_id:_ociResOid(),region:S.tfRegion||null}});alert('Instância parando...');tfLoadResources()}catch(e){alert('Erro: '+e.message)}}
|
||||
async function ociStartAdb(id){if(!confirm('Iniciar este Autonomous Database?'))return;try{await $api('/oci/autonomous-databases/'+id+'/action',{method:'POST',body:{action:'start',oci_config_id:_ociResOid(),region:S.tfRegion||null}});alert('Autonomous DB iniciando...');tfLoadResources()}catch(e){alert('Erro: '+e.message)}}
|
||||
async function ociStopAdb(id){if(!confirm('Parar este Autonomous Database?'))return;try{await $api('/oci/autonomous-databases/'+id+'/action',{method:'POST',body:{action:'stop',oci_config_id:_ociResOid(),region:S.tfRegion||null}});alert('Autonomous DB parando...');tfLoadResources()}catch(e){alert('Erro: '+e.message)}}
|
||||
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)}
|
||||
}
|
||||
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)}
|
||||
}
|
||||
function tfSwitchBtab(tab){
|
||||
S.tfBtab=tab;S.tfEditIdx=-1;
|
||||
if(tab==='resources'&&!S.tfResources&&S.tfCompartment)tfLoadResources();
|
||||
@@ -1051,10 +1055,18 @@ async function tfSend(){
|
||||
const el=document.getElementById('tfi');const m=el.value.trim();if(!m)return;el.style.height='auto';S._tfiVal='';
|
||||
if(!S.tfModel){S.tfMsgs.push({r:'assistant',c:'⚠️ Selecione um modelo antes de enviar.',t:tstamp()});R();return}
|
||||
if(!S.tfCompartment){S.tfMsgs.push({r:'assistant',c:'⚠️ Selecione um compartment antes de enviar.',t:tstamp()});R();return}
|
||||
el.value='';S.tfMsgs.push({r:'user',c:m,t:tstamp()});R();tfScroll();
|
||||
el.value='';S.tfMsgs.push({r:'user',c:m,t:tstamp()});R();tfScroll(); // show original msg without injected files
|
||||
S.tfMsgs.push({r:'assistant',c:'⏳ _Gerando Terraform..._',thinking:true});R();tfScroll();
|
||||
try{
|
||||
const body={message:m,session_id:S.tfSid,use_tools:false};
|
||||
// Inject existing files context when user is correcting/fixing
|
||||
let finalMsg=m;
|
||||
if(S.tfFiles.length>0){
|
||||
const filesCtx=S.tfFiles.map(f=>'// filename: '+f.name+'\n'+f.content).join('\n\n');
|
||||
finalMsg+='\n\n---\nARQUIVOS TERRAFORM ATUAIS NO WORKSPACE (corrija estes arquivos):\n```hcl\n'+filesCtx+'\n```';
|
||||
}
|
||||
if(S.tfPlanOut){finalMsg+='\n\n---\nÚLTIMO OUTPUT DO TERRAFORM PLAN:\n```\n'+S.tfPlanOut.slice(-3000)+'\n```'}
|
||||
if(S.tfApplyOut){finalMsg+='\n\n---\nÚLTIMO OUTPUT DO TERRAFORM APPLY:\n```\n'+S.tfApplyOut.slice(-3000)+'\n```'}
|
||||
const body={message:finalMsg,session_id:S.tfSid,use_tools:false};
|
||||
if(S.tfModel.startsWith('cfg:'))body.genai_config_id=S.tfModel.slice(4);
|
||||
else{
|
||||
if(!S.tfOci){S.tfMsgs.pop();S.tfMsgs.push({r:'assistant',c:'⚠️ Selecione uma credencial OCI.',t:tstamp()});R();return}
|
||||
@@ -1264,11 +1276,18 @@ function renderExpData(d){if(!d)return'<div class="emp"><p>Selecione um compartm
|
||||
if(d.error)return`<div class="al al-e">${d.error}</div>`;if(!Array.isArray(d)||!d.length)return'<div class="emp"><p>Nenhum recurso encontrado neste compartment.</p></div>';
|
||||
if(S.expResType==='security_lists')return rExpSecLists(d);
|
||||
if(S.expResType==='route_tables')return rExpRouteTables(d);
|
||||
const isInst=S.expResType==='instances';
|
||||
const isAdb=S.expResType==='databases';
|
||||
return`<div style="font-size:.74rem;color:var(--t4);margin-bottom:.5rem">${d.length} item(ns) encontrado(s)</div><div class="exp-r">${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'];
|
||||
return`<div class="exp-c"><strong>${name}</strong>${i.lifecycle_state?` <span class="badge ${i.lifecycle_state==='RUNNING'||i.lifecycle_state==='ACTIVE'||i.lifecycle_state==='AVAILABLE'?'b-pass':'b-pend'}">${i.lifecycle_state}</span>`:''}<br>
|
||||
let actionBtn='';
|
||||
if(isInst&&i.lifecycle_state==='RUNNING')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:6px" onclick="event.stopPropagation();ociInstanceAction(\''+i.id+'\',\'STOP\')">■ Stop</button>';
|
||||
else if(isInst&&i.lifecycle_state==='STOPPED')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:6px" onclick="event.stopPropagation();ociInstanceAction(\''+i.id+'\',\'START\')">▶ Start</button>';
|
||||
else if(isAdb&&i.lifecycle_state==='AVAILABLE')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:6px" onclick="event.stopPropagation();ociAdbAction(\''+i.id+'\',\'stop\')">■ Stop</button>';
|
||||
else if(isAdb&&i.lifecycle_state==='STOPPED')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:6px" onclick="event.stopPropagation();ociAdbAction(\''+i.id+'\',\'start\')">▶ Start</button>';
|
||||
return`<div class="exp-c"><strong>${name}</strong>${i.lifecycle_state?` <span class="badge ${i.lifecycle_state==='RUNNING'||i.lifecycle_state==='ACTIVE'||i.lifecycle_state==='AVAILABLE'?'b-pass':'b-pend'}">${i.lifecycle_state}</span>`:''}${actionBtn}<br>
|
||||
${keys.filter(k=>!skip.includes(k)).map(k=>`<span style="color:var(--t4);font-size:.66rem">${k}:</span> <span class="em">${typeof i[k]==='object'?JSON.stringify(i[k]):i[k]}</span>`).join('<br>')}</div>`}).join('')}</div>`}
|
||||
function rExpSecLists(d){
|
||||
return`<div style="font-size:.74rem;color:var(--t4);margin-bottom:.5rem">${d.length} security list(s)</div>${d.map((sl,idx)=>{
|
||||
|
||||
Reference in New Issue
Block a user