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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user