feat: LAD A-Team CIS Compliance Report, React SPA, i18n, report management, explorer UX fixes
- Professional Oracle-format compliance report with RAG remediation from ADB vector store - React 19 SPA at /app/ (16 pages, TypeScript, Vite, Zustand, i18n pt/en 625 keys) - Report delete endpoint, embed individual report files into vector store - Terraform ZIP download (client-side, pure JS) - Explorer silent polling during start/stop (no flickering) - HTML report and compliance report sections start minimized - Token auth fix for compliance report CSV download links
This commit is contained in:
@@ -1433,7 +1433,7 @@ function rTerraform(){
|
||||
Resources${S.tfResources?(' ('+Object.values(S.tfResources).reduce((a,b)=>a+(Array.isArray(b)?b.length:0),0)+')'):''}
|
||||
</div>
|
||||
<div class="tf-bottom-actions">
|
||||
${S.tfFiles.length?'<button class="btn bs bsm" onclick="tfDlAll()" style="font-size:.66rem;padding:2px 8px">Download All</button>':''}
|
||||
${S.tfFiles.length?'<button class="btn bs bsm" onclick="tfDlAll()" style="font-size:.66rem;padding:2px 8px">Download ZIP</button>':''}
|
||||
${rTfActions()}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1605,11 +1605,44 @@ function tfDlFile(i){
|
||||
const b=new Blob([f.content],{type:'text/plain'});const a=document.createElement('a');a.href=URL.createObjectURL(b);a.download=f.name;a.click();URL.revokeObjectURL(a.href);
|
||||
}
|
||||
function tfDlAll(){
|
||||
// Download each file
|
||||
const files=[...S.tfFiles];
|
||||
const oc=S.ociCfg.find(x=>x.id===S.tfOci);
|
||||
if(oc){const comp=S.tfCompartment||oc.compartment_id||'';if(!files.find(f=>f.name==='terraform.tfvars'))files.push({name:'terraform.tfvars',content:`compartment_id = "${comp}"\nregion = "${oc.region}"`,auto:true})}
|
||||
files.forEach((f,i)=>{setTimeout(()=>{const b=new Blob([f.content],{type:'text/plain'});const a=document.createElement('a');a.href=URL.createObjectURL(b);a.download=f.name;a.click();URL.revokeObjectURL(a.href)},i*200)});
|
||||
// Build ZIP using raw deflate-less store (no external lib needed)
|
||||
const enc=new TextEncoder();
|
||||
const parts=[];const centralDir=[];let offset=0;
|
||||
for(const f of files){
|
||||
const data=enc.encode(f.content);const nameB=enc.encode(f.name);
|
||||
const now=new Date();const time=((now.getHours()<<11)|(now.getMinutes()<<5)|(now.getSeconds()>>1))&0xFFFF;
|
||||
const date=(((now.getFullYear()-1980)<<9)|((now.getMonth()+1)<<5)|now.getDate())&0xFFFF;
|
||||
const crc=_crc32(data);
|
||||
// Local file header (30 + name + data)
|
||||
const lh=new Uint8Array(30+nameB.length+data.length);const lv=new DataView(lh.buffer);
|
||||
lv.setUint32(0,0x04034b50,true);lv.setUint16(4,20,true);lv.setUint16(6,0,true);lv.setUint16(8,0,true);
|
||||
lv.setUint16(10,time,true);lv.setUint16(12,date,true);lv.setUint32(14,crc,true);
|
||||
lv.setUint32(18,data.length,true);lv.setUint32(22,data.length,true);lv.setUint16(26,nameB.length,true);
|
||||
lh.set(nameB,30);lh.set(data,30+nameB.length);
|
||||
parts.push(lh);
|
||||
// Central directory entry (46 + name)
|
||||
const cd=new Uint8Array(46+nameB.length);const cv=new DataView(cd.buffer);
|
||||
cv.setUint32(0,0x02014b50,true);cv.setUint16(4,20,true);cv.setUint16(6,20,true);cv.setUint16(8,0,true);
|
||||
cv.setUint16(10,0,true);cv.setUint16(12,time,true);cv.setUint16(14,date,true);cv.setUint32(16,crc,true);
|
||||
cv.setUint32(20,data.length,true);cv.setUint32(24,data.length,true);cv.setUint16(28,nameB.length,true);
|
||||
cv.setUint32(42,offset,true);cd.set(nameB,46);
|
||||
centralDir.push(cd);
|
||||
offset+=lh.length;
|
||||
}
|
||||
const cdSize=centralDir.reduce((s,c)=>s+c.length,0);
|
||||
const eocd=new Uint8Array(22);const ev=new DataView(eocd.buffer);
|
||||
ev.setUint32(0,0x06054b50,true);ev.setUint16(8,files.length,true);ev.setUint16(10,files.length,true);
|
||||
ev.setUint32(12,cdSize,true);ev.setUint32(16,offset,true);
|
||||
const blob=new Blob([...parts,...centralDir,eocd],{type:'application/zip'});
|
||||
const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='terraform.zip';a.click();URL.revokeObjectURL(a.href);
|
||||
}
|
||||
function _crc32(buf){
|
||||
let crc=0xFFFFFFFF;
|
||||
for(let i=0;i<buf.length;i++){crc^=buf[i];for(let j=0;j<8;j++)crc=(crc>>>1)^(crc&1?0xEDB88320:0)}
|
||||
return(crc^0xFFFFFFFF)>>>0;
|
||||
}
|
||||
|
||||
function tfPickModel(v){S.tfModel=v;S.tfMdOpen=false;if(v&&!v.startsWith('cfg:')&&!S.tfOci&&S.ociCfg.length){S.tfOci=S.ociCfg[0].id;S.tfRegion=S.ociCfg[0].region;S.tfCompartment=S.ociCfg[0].compartment_id||'';tfLoadComps()}else if(v&&v.startsWith('cfg:')){const g=S.genaiCfg.find(x=>x.id===v.slice(4));if(g&&g.oci_config_id&&!S.tfOci){S.tfOci=g.oci_config_id;const c=S.ociCfg.find(x=>x.id===g.oci_config_id);if(c){S.tfRegion=c.region;S.tfCompartment=c.compartment_id||''}tfLoadComps()}}R()}
|
||||
@@ -1725,7 +1758,7 @@ function tfSwitchBtab(tab){
|
||||
const left=document.querySelector('.tf-bottom-content');if(left)left.innerHTML=rTfBottomContent();
|
||||
const tabs=document.querySelectorAll('.tf-btab');tabs.forEach(t=>{const isActive=t.textContent.trim().toLowerCase().startsWith(tab);t.classList.toggle('active',isActive)});
|
||||
// Update actions
|
||||
const acts=document.querySelector('.tf-bottom-actions');if(acts){acts.innerHTML=(S.tfFiles.length?'<button class="btn bs bsm" onclick="tfDlAll()" style="font-size:.66rem;padding:2px 8px">Download All</button>':'')+rTfActions()}
|
||||
const acts=document.querySelector('.tf-bottom-actions');if(acts){acts.innerHTML=(S.tfFiles.length?'<button class="btn bs bsm" onclick="tfDlAll()" style="font-size:.66rem;padding:2px 8px">Download ZIP</button>':'')+rTfActions()}
|
||||
}
|
||||
function tfToggleModelDrop(){const dd=document.getElementById('tfmdd');dd.classList.toggle('open');S.tfMdOpen=dd.classList.contains('open');if(S.tfMdOpen){const inp=document.getElementById('tfmds');if(inp){inp.value='';inp.focus();tfFilterModels('')}}}
|
||||
function tfFilterModels(q){const list=document.getElementById('tfmdl');if(!list)return;const items=list.querySelectorAll('.itm,.grp');const ql=q.toLowerCase();let lastGrp=null,vis=false;items.forEach(el=>{if(el.classList.contains('grp')){if(lastGrp)lastGrp.style.display=vis?'':'none';lastGrp=el;vis=false}else{const show=!q||el.textContent.toLowerCase().includes(ql);el.style.display=show?'':'none';if(show)vis=true}});if(lastGrp)lastGrp.style.display=vis?'':'none'}
|
||||
|
||||
Reference in New Issue
Block a user