refactor: reorganize Reports and Downloads tabs, update README
Move report history/status table to Reports tab. Simplify Downloads tab to show only completed reports with expandable file browser grouped by category. Add CIS Level description tooltip and fix log expand persisting during report generation polling.
This commit is contained in:
@@ -218,7 +218,7 @@ const S={user:null,token:null,tab:'chat',msgs:[],sid:null,reports:[],ociCfg:[],g
|
||||
chatPrompts:[],editingPrompt:null,trackingReportId:null,ociRegions:{},rptSelRegions:[],rptRegionsOpen:false,rptRegionFilter:'',
|
||||
rptOciOpen:false,rptOciFilter:'',rptOciVal:'',rptRselOpen:false,rptRselFilter:'',rptRselVal:'',ociFormRegOpen:false,ociFormRegFilter:'',ociFormRegVal:'',
|
||||
rptLevel:2,rptObp:false,rptRaw:false,rptRedact:false,reportFiles:{},dlExpandedRid:null,dlTenancyFilter:'',
|
||||
cisVer:null,cisCheckResult:null,cisUpdating:false};
|
||||
cisVer:null,cisCheckResult:null,cisUpdating:false,rptHistFilter:''};
|
||||
const API='/api';
|
||||
|
||||
async function $api(p,o={}){const h={...(o.headers||{})};if(S.token)h['Authorization']='Bearer '+S.token;
|
||||
@@ -406,7 +406,7 @@ function rReport(){const comp=S.reports.filter(r=>r.status==='completed');
|
||||
const m=last.match(/\[(\d+)\/(\d+)\]/);const pct=m?Math.round((parseInt(m[1])/parseInt(m[2]))*100):0;
|
||||
progressHtml=`<div id="rpt-progress">${rProgressCard('running',last,pct,lines,S.trackingReportId)}</div>`;
|
||||
if(!_rptPollTimer)startRptPoll()}
|
||||
if(!comp.length)return`${progressHtml}<div class="cd"><div class="ct">📊 Compliance Reports</div><div class="emp"><div class="eic">📄</div><p>Nenhum relatório gerado ainda.</p></div></div>${rRunRpt()}`;
|
||||
if(!comp.length)return`${progressHtml}<div class="cd"><div class="ct">📊 Compliance Reports</div><div class="emp"><div class="eic">📄</div><p>Nenhum relatório gerado ainda.</p></div></div>${rRunRpt()}${rReportHistory()}`;
|
||||
const lt=comp[0];
|
||||
const selRpt=S.rptRselVal?comp.find(r=>r.id===S.rptRselVal):lt;
|
||||
if(!S.rptRselVal)S.rptRselVal=lt.id;
|
||||
@@ -421,7 +421,7 @@ ${S.rptRselOpen?`<div class="mdrop-list" style="position:absolute;top:100%;left:
|
||||
${rselDdItems||'<div style="padding:.5rem;color:var(--t4);font-size:.72rem">Nenhum relatório encontrado</div>'}
|
||||
</div>`:''}
|
||||
</div></div>
|
||||
<iframe class="rif" id="rfr" src="${API}/reports/${(selRpt||lt).id}/html"></iframe>${rRunRpt()}`}
|
||||
<iframe class="rif" id="rfr" src="${API}/reports/${(selRpt||lt).id}/html"></iframe>${rRunRpt()}${rReportHistory()}`}
|
||||
function rRunRpt(){if(S.user?.role==='viewer')return'';
|
||||
const selTags=S.rptSelRegions.map(r=>`<span class="tag" style="background:var(--b2);font-size:.62rem;display:inline-flex;align-items:center;gap:.2rem">${r}<span onclick="rptRemoveRegion('${r}')" style="cursor:pointer;font-weight:700;color:var(--err)">×</span></span>`).join(' ');
|
||||
const f=S.rptRegionFilter.toLowerCase();
|
||||
@@ -509,7 +509,9 @@ async function pollRptProgress(){if(!S.trackingReportId)return stopRptPoll();
|
||||
if(ft.includes('writing cis reports'))pct=75;
|
||||
if(ft.includes('best practices'))pct=85;
|
||||
if(ft.includes('finished'))pct=100;
|
||||
el.innerHTML=rProgressCard(p.status,last,pct,lines,S.trackingReportId)}
|
||||
const wasOpen=el.querySelector('details[open]')!==null;
|
||||
el.innerHTML=rProgressCard(p.status,last,pct,lines,S.trackingReportId);
|
||||
if(wasOpen){const d=el.querySelector('details');if(d)d.open=true}}
|
||||
if(p.status==='completed'||p.status==='failed'||p.status==='cancelled'){stopRptPoll();S.trackingReportId=null;S.reports=await $api('/reports');R()}}catch(e){}}
|
||||
async function cancelRpt(rid){if(!confirm('Cancelar este relatório?'))return;
|
||||
try{await $api('/reports/'+rid+'/cancel',{method:'POST'});stopRptPoll();S.trackingReportId=null;S.reports=await $api('/reports');R()}catch(e){alert('Erro: '+e.message)}}
|
||||
@@ -525,33 +527,52 @@ function rProgressCard(status,lastStep,pct,lines,rid){
|
||||
${lines.length>1?`<details style="margin-top:.35rem"><summary style="font-size:.68rem;color:var(--t4);cursor:pointer">Ver log completo</summary><pre style="font-size:.62rem;color:var(--t4);margin-top:.25rem;max-height:120px;overflow:auto;white-space:pre-wrap">${lines.join('\n')}</pre></details>`:''}
|
||||
</div></div>`}
|
||||
|
||||
/* ── Downloads ── */
|
||||
function rDl(){
|
||||
/* ── Report History (inside Reports tab) ── */
|
||||
function rReportHistory(){
|
||||
const tenancies=[...new Set(S.reports.map(r=>r.tenancy_name))].sort();
|
||||
const filtered=S.dlTenancyFilter?S.reports.filter(r=>r.tenancy_name===S.dlTenancyFilter):S.reports;
|
||||
return`<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:.55rem;gap:.5rem;flex-wrap:wrap">
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
<select style="font-size:.76rem;min-width:180px" onchange="S.dlTenancyFilter=this.value;S.dlExpandedRid=null;R()">
|
||||
const filtered=S.rptHistFilter?S.reports.filter(r=>r.tenancy_name===S.rptHistFilter):S.reports;
|
||||
if(!filtered.length&&!S.reports.length)return'';
|
||||
return`<div class="cd" style="margin-top:.85rem"><div class="ct">📋 Histórico de Relatórios</div>
|
||||
<div style="display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem">
|
||||
<select style="font-size:.76rem;min-width:180px" onchange="S.rptHistFilter=this.value;R()">
|
||||
<option value="">Todas as Tenancies (${S.reports.length})</option>
|
||||
${tenancies.map(t=>`<option value="${t}" ${S.dlTenancyFilter===t?'selected':''}>${t} (${S.reports.filter(r=>r.tenancy_name===t).length})</option>`).join('')}
|
||||
${tenancies.map(t=>`<option value="${t}" ${S.rptHistFilter===t?'selected':''}>${t} (${S.reports.filter(r=>r.tenancy_name===t).length})</option>`).join('')}
|
||||
</select>
|
||||
<span style="font-size:.72rem;color:var(--t4)">${filtered.length} report(s)</span></div>
|
||||
<button class="btn bs bsm" onclick="refreshDl()">🔄 Atualizar</button></div>
|
||||
<div class="cd"><table><thead><tr><th>Tenancy</th><th>Level</th><th>Opções</th><th>Status</th><th>Executado</th><th>Concluído</th><th>Arquivos</th><th>Ações</th></tr></thead>
|
||||
<table><thead><tr><th>Tenancy</th><th>Level</th><th>Opções</th><th>Status</th><th>Executado</th><th>Concluído</th><th>Ações</th></tr></thead>
|
||||
<tbody>${filtered.map(r=>{
|
||||
const opts=[];if(r.obp_checks)opts.push('OBP');if(r.raw_data)opts.push('Raw');if(r.redact_output)opts.push('Redact');
|
||||
const isExp=S.dlExpandedRid===r.id;
|
||||
const fHtml=isExp&&S.reportFiles[r.id]?renderFileList(r.id,S.reportFiles[r.id]):'';
|
||||
return`<tr><td><strong>${r.tenancy_name}</strong></td>
|
||||
<td style="font-size:.72rem">${r.level||2}</td>
|
||||
<td style="font-size:.68rem">${opts.join(', ')||'—'}</td>
|
||||
<td><span class="badge ${r.status==='completed'?'b-pass':r.status==='failed'||r.status==='cancelled'?'b-fail':'b-pend'}">${r.status==='cancelled'?'cancelado':r.status}</span></td>
|
||||
<td style="font-size:.72rem">${r.created_at||'—'}</td>
|
||||
<td style="font-size:.72rem">${r.completed_at||'—'}</td>
|
||||
<td>${r.status==='completed'?`<button class="btn bs bsm" style="font-size:.68rem" onclick="toggleFiles('${r.id}')">${isExp?'Ocultar':'Ver'} Arquivos</button>`:'—'}</td>
|
||||
<td>${r.status==='completed'?`<button class="btn bs bsm" onclick="dlRpt('${r.id}','html')">HTML</button> <button class="btn bs bsm" onclick="dlRpt('${r.id}','json')">JSON</button>`:r.status==='running'?`<button class="btn bs bsm" style="color:var(--err);border-color:var(--err)" onclick="cancelRpt('${r.id}')">Cancelar</button>`:'—'}</td></tr>
|
||||
${isExp?`<tr><td colspan="8" style="padding:0">${fHtml}</td></tr>`:''}`}).join('')}</tbody></table>
|
||||
${!filtered.length?'<div class="emp"><div class="eic">📂</div><p>Nenhum relatório.</p></div>':''}</div>`}
|
||||
<td>${r.status==='completed'?`<button class="btn bs bsm" onclick="dlRpt('${r.id}','html')">HTML</button> <button class="btn bs bsm" onclick="dlRpt('${r.id}','json')">JSON</button>`:r.status==='running'?`<button class="btn bs bsm" style="color:var(--err);border-color:var(--err)" onclick="cancelRpt('${r.id}')">Cancelar</button>`:'—'}</td></tr>`}).join('')}</tbody></table>
|
||||
${!filtered.length?'<div class="emp" style="padding:.5rem"><p style="font-size:.72rem;color:var(--t4)">Nenhum relatório.</p></div>':''}</div>`}
|
||||
|
||||
/* ── Downloads (file browser only) ── */
|
||||
function rDl(){
|
||||
const comp=S.reports.filter(r=>r.status==='completed');
|
||||
const tenancies=[...new Set(comp.map(r=>r.tenancy_name))].sort();
|
||||
const filtered=S.dlTenancyFilter?comp.filter(r=>r.tenancy_name===S.dlTenancyFilter):comp;
|
||||
return`<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:.55rem;gap:.5rem;flex-wrap:wrap">
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
<select style="font-size:.76rem;min-width:180px" onchange="S.dlTenancyFilter=this.value;S.dlExpandedRid=null;R()">
|
||||
<option value="">Todas as Tenancies (${comp.length})</option>
|
||||
${tenancies.map(t=>`<option value="${t}" ${S.dlTenancyFilter===t?'selected':''}>${t} (${comp.filter(r=>r.tenancy_name===t).length})</option>`).join('')}
|
||||
</select>
|
||||
<span style="font-size:.72rem;color:var(--t4)">${filtered.length} report(s)</span></div>
|
||||
<button class="btn bs bsm" onclick="refreshDl()">🔄 Atualizar</button></div>
|
||||
${!filtered.length?'<div class="cd"><div class="emp"><div class="eic">📂</div><p>Nenhum relatório concluído.</p></div></div>':`
|
||||
<div style="display:flex;flex-direction:column;gap:.5rem">${filtered.map(r=>{
|
||||
const isExp=S.dlExpandedRid===r.id;
|
||||
const fHtml=isExp&&S.reportFiles[r.id]?renderFileList(r.id,S.reportFiles[r.id]):'';
|
||||
return`<div class="cd" style="padding:0;overflow:hidden">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;padding:.6rem .8rem;cursor:pointer" onclick="toggleFiles('${r.id}')">
|
||||
<div><strong style="font-size:.8rem">${r.tenancy_name}</strong><span style="font-size:.7rem;color:var(--t4);margin-left:.5rem">Level ${r.level||2} — ${r.created_at}</span></div>
|
||||
<span style="font-size:.7rem;color:var(--ac)">${isExp?'▲ Ocultar':'▼ Ver Arquivos'}</span></div>
|
||||
${isExp?fHtml:''}</div>`}).join('')}</div>`}`}
|
||||
async function toggleFiles(rid){if(S.dlExpandedRid===rid){S.dlExpandedRid=null;R();return}
|
||||
S.dlExpandedRid=rid;if(!S.reportFiles[rid]){try{S.reportFiles[rid]=await $api('/reports/'+rid+'/files')}catch(e){S.reportFiles[rid]=[]}}R()}
|
||||
function renderFileList(rid,files){if(!files.length)return'<div style="padding:.6rem;color:var(--t4);font-size:.72rem">Nenhum arquivo encontrado</div>';
|
||||
|
||||
Reference in New Issue
Block a user