/* ============================================================ FLOWSHIELD — Findings / Scans / Reports / Surface / misc ============================================================ */ function DemoBanner({ what }) { return (
Este workspace aún no tiene {what}. Lanza un escaneo y aparecerán aquí automáticamente.
); } function csvDownload(name, header, rows) { const esc = (x) => '"' + String(x == null ? '' : x).replace(/"/g, '""') + '"'; const csv = [header, ...rows].map(r => r.map(esc).join(',')).join('\n'); const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' })); a.download = name; document.body.appendChild(a); a.click(); a.remove(); } function Findings() { const [filter, setFilter] = useStateL('all'); const [sevF, setSevF] = useStateL('all'); const [raw, setRaw] = useStateL(null); // null = cargando const [counts, setCounts] = useStateL(null); const [demo, setDemo] = useStateL(false); const [drawer, setDrawer] = useStateL(null); // detalle del hallazgo (revelar credencial) const Drawer = window.RtFindingDrawer; const load = () => { window.fsApi('/api/results/findings', { method:'GET' }) .then(d => { const items = (d && d.findings) || []; setRaw(items); setCounts((d && d.counts) || null); setDemo(items.length === 0); }) .catch(() => { setRaw([]); setCounts(null); setDemo(false); }); }; useEffectL(() => { load(); }, []); const all = raw || []; const data = all.filter(f => (filter==='all'||f.status===filter) && (sevF==='all'||f.sev===sevF)); const c = counts ? counts.sev : all.reduce((a,f)=>{a[f.sev]=(a[f.sev]||0)+1;return a;},{}); const total = counts ? counts.total : all.length; const openN = counts ? counts.status.open : all.filter(f=>f.status==='open').length; const fixedN = counts ? counts.status.fixed : all.filter(f=>f.status==='fixed').length; const cycle = async (f) => { if (demo) return; const next = f.status==='open' ? 'triage' : f.status==='triage' ? 'fixed' : 'open'; try { await window.fsApi('/api/results/findings/'+f.id, { method:'PATCH', body:{ status: next } }); load(); } catch (e) { window.alert('No se pudo cambiar el estado: ' + (e.message||e)); } }; const exportCsv = () => csvDownload('flowshield-hallazgos.csv', ['id','hallazgo','objetivo','severidad','cvss','estado','origen','fecha'], all.map(f=>[f.code||f.id, f.name, f.target, f.sev, f.cvss, f.status, f.source, f.date])); const [pdfBusy, setPdfBusy] = useStateL(false); const exportPdf = async () => { if (demo) return; setPdfBusy(true); try { await window.fsDownloadPdf('/api/report/findings', {}); } catch (e) { window.alert('No se pudo generar el informe: ' + (e.message||e)); } setPdfBusy(false); }; return (
ActualizarCSV{pdfBusy?'Generando…':'Informe PDF'}} /> {demo && }
{[['all','Todos'],['open','Abiertos'],['triage','Triaje'],['fixed','Resueltos']].map(([k,l])=>( ))}
{[['all','Toda severidad'],['crit','Crítico'],['high','Alto'],['med','Medio'],['low','Bajo']].map(([k,l])=>( ))}
{raw === null && } {raw !== null && data.length === 0 && } {data.map(f => ( !demo && setDrawer({ mode:'detail', id:f.id })} style={{ cursor:demo?'default':'pointer' }}> ))}
IDHallazgoObjetivoSeveridadCVSSEstadoOrigenFecha
Cargando…
Sin hallazgos para este filtro.
{f.code || f.id} {f.has_secret && }{f.name} {f.target} {f.cvss} { e.stopPropagation(); cycle(f); }} title={demo?'':'Cambiar estado (abierto → triaje → resuelto)'} style={{ cursor:demo?'default':'pointer' }}> {f.source} {rptDate(f.date)}
{drawer && Drawer && { setDrawer(null); load(); }} />}
); } const _TEAM_LABEL = { red:'RED', blue:'BLUE', amber:'NORM', fs:'—' }; const _TEAM_COLOR = { red:'var(--red)', blue:'var(--blue)', amber:'var(--amber)', fs:'var(--txt-dim)' }; function Scans({ onNav, onRelaunch }) { const [raw, setRaw] = useStateL(null); const [demo, setDemo] = useStateL(false); const load = () => { window.fsApi('/api/results/scans', { method:'GET' }) .then(d => { const items = (d && d.scans) || []; setRaw(items); setDemo(items.length === 0); }) .catch(() => { setRaw([]); setDemo(false); }); }; useEffectL(() => { load(); }, []); const del = async (s) => { if (demo) return; if (!window.confirm('¿Borrar este escaneo y sus hallazgos?')) return; try { await window.fsApi('/api/results/scans/'+s.id, { method:'DELETE' }); load(); } catch (e) { window.alert('No se pudo borrar el escaneo: ' + (e.message||e)); } }; const clearData = async () => { if (!window.confirm('¿Vaciar los datos de Red Team del workspace? Se borrarán escaneos, hallazgos y activos descubiertos. Blue Team y Normativa se conservan.')) return; try { const r = await window.fsApi('/api/results/clear', { method:'POST', body:{} }); load(); window.alert('Datos vaciados: ' + (r.scans_deleted||0) + ' escaneos borrados.'); } catch (e) { window.alert('No se pudo vaciar: ' + (e.message||e)); } }; const list = raw || []; return (
Vaciar datosActualizaronNav && onNav('redteam')}>Nuevo escaneo} /> {demo && }
{raw === null && } {raw !== null && list.length === 0 && } {list.map(s => { const team = s.team || (s.module==='redteam'?'red':s.module==='blueteam'?'blue':s.module==='compliance'?'amber':'fs'); return ( );})}
IDHerramientaEquipoObjetivoEstadoResumenFecha
Cargando…
Aún no hay escaneos.
{s.code || s.id} {s.tool} {_TEAM_LABEL[team]} {s.target} {s.status==='running' ?
: }
{s.found && (s.found.crit||s.found.high||s.found.med||s.found.low) ?
{['crit','high','med','low'].map(k=>s.found[k]?{s.found[k]}:null)}
: }
{rptDate(s.date)} {!demo && <> {onRelaunch && onRelaunch(s)} title="Re-escanear con los mismos parámetros">Re-escanear} del(s)} style={{ color:'var(--danger)' }} title="Borrar escaneo y sus hallazgos">Borrar }
); } function rptDate(iso) { try { return new Date(iso).toLocaleString('es-ES', { dateStyle:'medium', timeStyle:'short' }); } catch { return iso || '—'; } } /* ---- Proyectos / engagements + informes ejecutivo/técnico/comercial ---- */ const PROJECT_REPORTS = [ ['executive', 'Ejecutivo', 'Resumen para dirección: postura de riesgo, prioridades y plan.'], ['technical', 'Técnico', 'Entregable completo: hallazgos detallados, superficie y CVE.'], ['commercial', 'Propuesta comercial', 'Exposición al riesgo + servicios de remediación propuestos.'], ]; function ProjField({ label, value, onChange, type, placeholder, rows }) { const common = { value: value ?? '', placeholder: placeholder || '', onChange: e => onChange(e.target.value), disabled: false }; return (
{rows ?