feat: Terraform intelligence, anti-flickering, and v2.2 release

- Official resource docs injection from registry.terraform.io (on-demand fetch + SQLite cache)
- Smart tfvars auto-generation from declared variables (tenancy_ocid, user_ocid, etc.)
- Multi-region provider: use var.region instead of hardcoded OCI config region
- RPC anti-cycle rule in system prompt
- System prompt auto-sync to DB on restart
- Fix UI flickering: animations scoped to tab switch only (tab-enter class)
- Terraform polling timeout increased to 60 min
- Terminal output updates without full page re-render
- Fix ${IC.bot} literal rendering in chat empty state
- Chat empty state icon scaled to 48px
- README updated to v2.2 with all changes
This commit is contained in:
nogueiraguh
2026-03-10 14:29:38 -03:00
parent 0877744cd1
commit 851def30b6
3 changed files with 247 additions and 45 deletions

View File

@@ -288,20 +288,20 @@ tbody tr:last-child td{border-bottom:none}
.ld{animation:pu 1.2s ease infinite;color:var(--t4);font-size:.82rem;text-align:center;padding:1.5rem}
.spinner{display:inline-block;width:16px;height:16px;border:2px solid var(--bd);border-top-color:var(--ac);border-radius:50%;animation:spin .6s linear infinite}
/* Page content fade-in */
.pc>.cd,.pc>.kpi-row,.pc>.exp-wrap,.pc>.ch-c,.pc>div{animation:fi .4s var(--trans) both}
.pc>.cd:nth-child(2),.pc>div:nth-child(2){animation-delay:.05s}
.pc>.cd:nth-child(3),.pc>div:nth-child(3){animation-delay:.1s}
.pc>.cd:nth-child(4),.pc>div:nth-child(4){animation-delay:.15s}
/* Page content fade-in — only on tab switch (class .tab-enter added by switchTab, removed after animation) */
.tab-enter>.cd,.tab-enter>.kpi-row,.tab-enter>.exp-wrap,.tab-enter>.ch-c,.tab-enter>div{animation:fi .4s var(--trans) both}
.tab-enter>.cd:nth-child(2),.tab-enter>div:nth-child(2){animation-delay:.05s}
.tab-enter>.cd:nth-child(3),.tab-enter>div:nth-child(3){animation-delay:.1s}
.tab-enter>.cd:nth-child(4),.tab-enter>div:nth-child(4){animation-delay:.15s}
/* Alert slide-in */
.al{padding:.65rem .85rem;border-radius:10px;font-size:.78rem;margin-bottom:.75rem;font-weight:500;animation:alertIn .3s var(--trans) both}
/* KPI card stagger */
.kpi{animation:fiScale .4s var(--trans) both}
.kpi:nth-child(2){animation-delay:.06s}
.kpi:nth-child(3){animation-delay:.12s}
.kpi:nth-child(4){animation-delay:.18s}
/* KPI card stagger — only on tab switch */
.tab-enter .kpi{animation:fiScale .4s var(--trans) both}
.tab-enter .kpi:nth-child(2){animation-delay:.06s}
.tab-enter .kpi:nth-child(3){animation-delay:.12s}
.tab-enter .kpi:nth-child(4){animation-delay:.18s}
/* Enhanced card hover */
.cd{transition:box-shadow .3s var(--trans),transform .3s var(--trans),border-color .3s var(--trans)}
@@ -319,20 +319,20 @@ tbody tr:last-child td{border-bottom:none}
.badge{transition:transform .15s var(--trans),box-shadow .15s var(--trans)}
.badge:hover{transform:scale(1.05)}
/* Table row slide-in */
tbody tr{animation:fi .3s var(--trans) both}
tbody tr:nth-child(2){animation-delay:.03s}
tbody tr:nth-child(3){animation-delay:.06s}
tbody tr:nth-child(4){animation-delay:.09s}
tbody tr:nth-child(5){animation-delay:.12s}
/* Table row slide-in — only on tab switch */
.tab-enter tbody tr{animation:fi .3s var(--trans) both}
.tab-enter tbody tr:nth-child(2){animation-delay:.03s}
.tab-enter tbody tr:nth-child(3){animation-delay:.06s}
.tab-enter tbody tr:nth-child(4){animation-delay:.09s}
.tab-enter tbody tr:nth-child(5){animation-delay:.12s}
/* Explorer card stagger */
.exp-c{animation:fiScale .3s var(--trans) both}
.exp-c:nth-child(2){animation-delay:.04s}
.exp-c:nth-child(3){animation-delay:.08s}
.exp-c:nth-child(4){animation-delay:.12s}
.exp-c:nth-child(5){animation-delay:.16s}
.exp-c:nth-child(6){animation-delay:.2s}
/* Explorer card stagger — only on tab switch */
.tab-enter .exp-c{animation:fiScale .3s var(--trans) both}
.tab-enter .exp-c:nth-child(2){animation-delay:.04s}
.tab-enter .exp-c:nth-child(3){animation-delay:.08s}
.tab-enter .exp-c:nth-child(4){animation-delay:.12s}
.tab-enter .exp-c:nth-child(5){animation-delay:.16s}
.tab-enter .exp-c:nth-child(6){animation-delay:.2s}
/* Nav item hover glow */
.ni:hover{background:var(--bg2);color:var(--t2);transform:translateX(2px)}
@@ -541,7 +541,9 @@ async function $api(p,o={}){const h={...(o.headers||{})};if(S.token)h['Authoriza
const d=await r.json();if(!r.ok)throw new Error(d.detail||d.message||'Erro');return d;}
async function doLogin(u,pw,totp){const b={username:u,password:pw};if(totp)b.totp_code=totp;
const d=await $api('/auth/login',{method:'POST',body:b});if(d.mfa_required)return d;
S.token=d.token;S.user=d.user;localStorage.setItem('t',d.token);await loadData();R();return d;}
S.token=d.token;S.user=d.user;localStorage.setItem('t',d.token);await loadData();R();
const _pg=document.getElementById('pg');if(_pg){const _pc=_pg.querySelector('.pc');if(_pc){_pc.classList.add('tab-enter');setTimeout(()=>_pc.classList.remove('tab-enter'),500)}}
return d;}
async function doLogout(){try{await $api('/auth/logout',{method:'POST'})}catch(e){}S.user=null;S.token=null;S.msgs=[];localStorage.removeItem('t');R()}
async function checkAuth(){const t=localStorage.getItem('t');if(!t)return;S.token=t;try{S.user=await $api('/users/me');await loadData()}catch(e){S.token=null;localStorage.removeItem('t')}}
async function loadData(){try{
@@ -587,7 +589,9 @@ function R(){try{
});
if(S.editing?.type==='mcp')setTimeout(mcpTypeFields,0)
}catch(e){console.error('Render error:',e);document.getElementById('app').innerHTML='<div style="padding:2rem;color:red"><h3>Render Error</h3><pre>'+e.message+'</pre></div>'}}
function switchTab(t){S.tab=t;R();if(t==='chat')loadHistory('chat');if(t==='terraform')loadHistory('terraform');if(t==='audit'&&S.user.role==='admin')loadAudit();if(t==='downloads')refreshDl();if(t==='explorer'&&S.ociCfg.length&&!S.expTree.length){if(!S.expCfg)S.expCfg=S.ociCfg[0].id;expLoadTree();expLoadRegions()}
function switchTab(t){S.tab=t;R();
const pg=document.getElementById('pg');if(pg){const pc=pg.querySelector('.pc');if(pc){pc.classList.add('tab-enter');setTimeout(()=>pc.classList.remove('tab-enter'),500)}}
if(t==='chat')loadHistory('chat');if(t==='terraform')loadHistory('terraform');if(t==='audit'&&S.user.role==='admin')loadAudit();if(t==='downloads')refreshDl();if(t==='explorer'&&S.ociCfg.length&&!S.expTree.length){if(!S.expCfg)S.expCfg=S.ociCfg[0].id;expLoadTree();expLoadRegions()}
const tm={'oci-config':'oci','genai':'genai','adb':'adb','mcp':'mcp'};if(tm[t])setTimeout(()=>refreshCLogs(tm[t]),100)}
/* ── Login ── */
@@ -1586,7 +1590,7 @@ async function tfCancel(){
}
async function tfPollExec(){
for(let i=0;i<600;i++){
for(let i=0;i<1800;i++){
await new Promise(r=>setTimeout(r,2000));
try{
const r=await $api('/terraform/workspaces/'+S.tfWs+'/status');
@@ -1594,10 +1598,16 @@ async function tfPollExec(){
S.tfApplyOut=r.apply_output||S.tfApplyOut;
S.tfDestroyOut=r.destroy_output||S.tfDestroyOut;
S.tfStatus=r.status;
if(!['planning','applying','destroying'].includes(r.status)){S.tfRunning=false;R();return}
R();
// Auto-scroll output
const t=document.getElementById('tfTermOut');if(t)t.scrollTop=t.scrollHeight;
const done=!['planning','applying','destroying'].includes(r.status);
if(done)S.tfRunning=false;
// Update only the terminal body without full re-render
const t=document.getElementById('tfTermOut');
if(t){
const content=S.tfDestroyOut||S.tfApplyOut||S.tfPlanOut||'';
t.innerHTML=content?escHtml(content):'<span style="opacity:.4">Aguardando execução...</span>';
t.scrollTop=t.scrollHeight;
}
if(done){R();return}
}catch(e){}
}
S.tfRunning=false;R();