feat: export/import configuration and multiple bug fixes

- Add export/import endpoints for full config backup (OCI, GenAI, MCP, prompts, settings, ADB)
- Export includes private keys (base64) for complete environment migration
- Import merges by ID (skips existing, no duplicates)
- Fix Resource Plan parser to recognize free-form format (model skips ```plan blocks)
- Fix replan not clearing previous apply/destroy outputs
- Fix duplicate CIS Compliance Scanner MCP server on multi-worker startup
- Add Terraform prompt editor with auto-save in menu
This commit is contained in:
nogueiraguh
2026-03-09 15:26:13 -03:00
parent c4a9f8d6d5
commit 188fc82858
2 changed files with 170 additions and 2 deletions

View File

@@ -1824,7 +1824,15 @@ ${ddItems||'<div style="padding:.5rem;color:var(--t4);font-size:.72rem">Nenhuma
</div>`:''}</div>`}
function ociFormPickReg(r){S.ociFormRegVal=r;S.ociFormRegOpen=false;S.ociFormRegFilter='';R()}
function rOci(){const eo=S.editing?.type==='oci'?S.ociCfg.find(c=>c.id===S.editing.id):null;
return`<div class="cd"><div class="ct">☁️ Credenciais Registradas</div><div id="ocm"></div>
return`<div class="cd" style="display:flex;align-items:center;justify-content:space-between;padding:.6rem 1rem">
<div style="font-size:.78rem;font-weight:600;color:var(--t2)">⚙️ Configuração do Sistema</div>
<div style="display:flex;gap:.4rem;align-items:center">
<button class="btn bp bsm" onclick="cfgExport()">📤 Exportar</button>
<button class="btn bs bsm" onclick="document.getElementById('cfgImportFile').click()">📥 Importar</button>
<input type="file" id="cfgImportFile" accept=".json" style="display:none" onchange="cfgImport(this)">
</div></div>
<div id="cfgMsg"></div>
<div class="cd"><div class="ct">☁️ Credenciais Registradas</div><div id="ocm"></div>
<table><thead><tr><th>Tenancy</th><th>Region</th><th>Detalhes</th><th>Ações</th></tr></thead><tbody>
${S.ociCfg.map(c=>`<tr${eo&&eo.id===c.id?' style="background:var(--bg3)"':''}><td><strong>${c.tenancy_name}</strong><br><span style="font-size:.66rem;color:var(--t4)">${c.created_at}</span></td><td><span class="tag">${c.region}</span></td>
<td style="font-family:var(--fm);font-size:.66rem;color:var(--t4);line-height:1.6"><span title="Fingerprint">🔑 ••••••••••••••••••••</span><br><span title="Tenancy OCID">🏢 ${c.tenancy_ocid}</span><br><span title="User OCID">👤 ${c.user_ocid}</span><br><span title="Compartment">📦 ${c.compartment_id||'—'}</span></td>
@@ -1862,6 +1870,39 @@ async function sOci(){const tn=document.getElementById('otn').value;
const btn=document.getElementById('obtn');btn.disabled=true;btn.textContent='Salvando...';sm('om','<span class="spinner" style="display:inline-block;width:14px;height:14px;vertical-align:middle"></span> Salvando credencial...','i');
try{const url=isEdit?'/oci/configs/'+S.editing.id:'/oci/config';const method=isEdit?'PUT':'POST';
await $api(url,{method,body:fd,headers:{}});S.editing=null;S.ociFormRegVal='';S.ociFormRegFilter='';S.ociCfg=await $api('/oci/configs');sm('om','✅ Credencial '+(isEdit?'atualizada':'salva')+' com sucesso!','s');R()}catch(e){sm('om',e.message,'e');btn.disabled=false;btn.textContent=isEdit?'Salvar Alterações':'Salvar Credencial'}}
async function cfgExport(){
try{
const resp=await fetch(API+'/config/export',{headers:S.token?{'Authorization':'Bearer '+S.token}:{}});
if(!resp.ok)throw new Error('Erro ao exportar');
const blob=await resp.blob();
const url=URL.createObjectURL(blob);
const a=document.createElement('a');a.href=url;
a.download=resp.headers.get('content-disposition')?.match(/filename="(.+)"/)?.[1]||'config-export.json';
a.click();URL.revokeObjectURL(url);
sm('cfgMsg','✅ Configuração exportada com sucesso!','s');
}catch(e){sm('cfgMsg','❌ '+e.message,'e')}
}
async function cfgImport(input){
const file=input.files[0];if(!file)return;
try{
const fd=new FormData();fd.append('file',file);
const resp=await fetch(API+'/config/import',{method:'POST',headers:S.token?{'Authorization':'Bearer '+S.token}:{},body:fd});
if(!resp.ok){const err=await resp.json();throw new Error(err.detail||'Erro ao importar')}
const d=await resp.json();
const parts=[];
if(d.oci_configs)parts.push(d.oci_configs+' OCI configs');
if(d.genai_configs)parts.push(d.genai_configs+' GenAI configs');
if(d.mcp_servers)parts.push(d.mcp_servers+' MCP servers');
if(d.system_prompts)parts.push(d.system_prompts+' prompts');
if(d.app_settings)parts.push(d.app_settings+' settings');
if(d.adb_vector_configs)parts.push(d.adb_vector_configs+' ADB configs');
if(d.skipped)parts.push(d.skipped+' ignorados (já existem)');
sm('cfgMsg','✅ Importado: '+parts.join(', '),'s');
[S.ociCfg,S.genaiCfg,S.mcpSvr]=await Promise.all([$api('/oci/configs'),$api('/genai/configs'),$api('/mcp/servers')]);
R();
}catch(e){sm('cfgMsg','❌ '+e.message,'e')}
finally{input.value=''}
}
async function tOci(id){try{const d=await $api('/oci/test/'+id,{method:'POST'});sm('ocm',d.status==='success'?'✅ Conexão OK!':'❌ '+d.message,d.status==='success'?'s':'e');refreshCLogs('oci')}catch(e){sm('ocm',e.message,'e')}}
async function dOci(id){if(!confirm('Excluir credencial?'))return;await $api('/oci/configs/'+id,{method:'DELETE'});S.ociCfg=await $api('/oci/configs');R()}