@@ -1955,16 +1955,23 @@ const MODULE_RENDERERS = {
19551955
19561956function getUnifiedTableColumns ( activeKind ) {
19571957 const active = String ( activeKind || '' ) ;
1958- const moduleTab = active . startsWith ( 'mod:' ) ? active . slice ( 4 ) : ( active === 'misconfig' ? 'misconfig' : '' ) ;
1958+ const moduleTab = active . startsWith ( 'mod:' ) ? active . slice ( 4 ) : (
1959+ active === 'misconfig' ? 'misconfig'
1960+ : active === 'nuclei' ? 'nuclei'
1961+ : active === 'ffuf' ? 'ffuf-fuzzing'
1962+ : ( active === 'apkx' || active . startsWith ( 'apkcat:' ) ) ? 'apkx' : ''
1963+ ) ;
19591964 switch ( moduleTab ) {
1965+ case 'apkx' :
1966+ return [ 'PATH' , 'CATEGORY' , 'MATCHER VALUE' , 'MODULE' ] ;
19601967 case 'nuclei' :
19611968 return [ 'TARGET' , 'SEV' , 'TEMPLATE' , 'MATCH' ] ;
19621969 case 'gf-patterns' :
19631970 return [ 'TARGET' , 'PATTERN' , 'VALUE' , 'SOURCE' ] ;
19641971 case 'misconfig' :
19651972 return [ 'TARGET' , 'SEV' , 'SERVICE' , 'FINDING' ] ;
19661973 case 'ffuf-fuzzing' :
1967- return [ 'TARGET ' , 'STATUS' , 'PATH/ WORD' , 'DETAIL ' ] ;
1974+ return [ 'URL ' , 'STATUS' , 'WORD' , 'LENGTH ' ] ;
19681975 default :
19691976 return [ 'TARGET' , 'SEV' , 'VULNERABILITY TYPE' , 'MODULE' ] ;
19701977 }
@@ -4536,6 +4543,18 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
45364543
45374544
45384545 let searchJsOnly = false ;
4546+ let presetMode = 'smart' ;
4547+ let quickChip = 'none' ;
4548+ let currentRenderedRows = [ ] ;
4549+ const presetStorageKey = `autoar.recon.filtersets.${ stNorm || 'generic' } ` ;
4550+ let savedFilterSets = { } ;
4551+
4552+ const loadSavedSets = ( ) => {
4553+ try { savedFilterSets = JSON . parse ( localStorage . getItem ( presetStorageKey ) || '{}' ) || { } ; } catch { savedFilterSets = { } ; }
4554+ } ;
4555+ const persistSavedSets = ( ) => {
4556+ try { localStorage . setItem ( presetStorageKey , JSON . stringify ( savedFilterSets ) ) ; } catch ( _ ) { }
4557+ } ;
45394558
45404559 const rowMatch = ( r ) => {
45414560 const k = r . kind || 'other' ;
@@ -4558,6 +4577,14 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
45584577 const sev = String ( r . severity || 'info' ) . toLowerCase ( ) ;
45594578 if ( sev !== filterSeverity ) return false ;
45604579 }
4580+ const sev = String ( r . severity || 'info' ) . toLowerCase ( ) ;
4581+ const targetStr = String ( r . target || '' ) . toLowerCase ( ) ;
4582+ const findingStr = String ( r . finding || '' ) . toLowerCase ( ) ;
4583+ if ( quickChip === 'highplus' && ! ( sev === 'high' || sev === 'critical' ) ) return false ;
4584+ if ( quickChip === 'hasurl' && ! ( / h t t p s ? : \/ \/ / i. test ( targetStr ) || / h t t p s ? : \/ \/ / i. test ( findingStr ) ) ) return false ;
4585+ if ( quickChip === 'exported' && ! ( findingStr . includes ( 'exported' ) || String ( r . apk_category || '' ) . toLowerCase ( ) . includes ( 'exported' ) ) ) return false ;
4586+ if ( quickChip === 'secrets' && ! / ( s e c r e t | t o k e n | a p i k e y | a p i k e y | p a s s w o r d | a u t h o r i z a t i o n ) / i. test ( findingStr ) ) return false ;
4587+ if ( quickChip === 'onlyjs' && ! ( / \. m ? j s x ? ( \? | $ ) / i. test ( targetStr ) || findingStr . includes ( 'javascript' ) || String ( r . apk_category || '' ) . toLowerCase ( ) . includes ( 'js' ) ) ) return false ;
45614588 return true ;
45624589 } ;
45634590
@@ -4568,7 +4595,7 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
45684595 <div style="padding:10px 12px;border-bottom:1px solid var(--border);font-size:11px;color:var(--text-muted);letter-spacing:.6px;text-transform:uppercase">Findings Views</div>
45694596 <div id="recon-left-rail" style="display:flex;flex-direction:column;gap:6px;padding:8px;overflow-y:auto;overflow-x:hidden;max-height:780px;scrollbar-width:thin"></div>
45704597 </aside>
4571- <section style="min-width:0">
4598+ <section style="min-width:0;position:relative ">
45724599 <div id="recon-apk-meta" style="display:none;padding:10px 12px;border-bottom:1px solid var(--border);background:rgba(34,211,238,.06)"></div>
45734600 <div id="recon-filter-bar" style="display:grid;grid-template-columns:minmax(200px,1.5fr) 140px 140px minmax(180px,1fr) auto;gap:8px;padding:10px;border-bottom:1px solid var(--border);background:rgba(2,6,23,.5)">
45744601 <input id="recon-filter-host" type="search" placeholder="🔍 Filter by target URL..." style="padding:8px 10px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);font-size:12px"/>
@@ -4591,6 +4618,21 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
45914618 <span><span id="recon-unified-shown">0</span> rows</span>
45924619 </div>
45934620 </div>
4621+ <div id="recon-quick-tools" style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 10px;border-bottom:1px solid var(--border);background:rgba(2,6,23,.38)">
4622+ <div id="recon-quick-chips" style="display:flex;align-items:center;gap:6px;flex-wrap:wrap"></div>
4623+ <div style="margin-left:auto;display:flex;align-items:center;gap:6px">
4624+ <select id="recon-view-mode" style="padding:6px 8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);font-size:11px">
4625+ <option value="smart">Smart columns</option>
4626+ <option value="raw">Raw columns</option>
4627+ </select>
4628+ <select id="recon-saved-filters" style="min-width:160px;padding:6px 8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);font-size:11px">
4629+ <option value="">Saved filters…</option>
4630+ </select>
4631+ <input id="recon-filter-name" type="text" placeholder="Filter name" style="width:130px;padding:6px 8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);font-size:11px"/>
4632+ <button id="recon-save-filter" type="button" style="padding:6px 10px;border:1px solid var(--border);border-radius:6px;background:rgba(34,211,238,.1);color:var(--accent-cyan);font-size:11px;cursor:pointer">Save</button>
4633+ <button id="recon-delete-filter" type="button" style="padding:6px 10px;border:1px solid var(--border);border-radius:6px;background:rgba(248,113,113,.08);color:#fca5a5;font-size:11px;cursor:pointer">Delete</button>
4634+ </div>
4635+ </div>
45944636 <!-- Standard findings table -->
45954637 <div id="recon-standard-view">
45964638 <div class="result-table-wrap" style="max-height:640px;overflow:auto">
@@ -4624,6 +4666,13 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
46244666 <div id="recon-assets-content" style="padding:16px;min-height:200px;max-height:680px;overflow:auto">
46254667 <div style="text-align:center;padding:40px;color:var(--text-muted)">Loading assets…</div>
46264668 </div>
4669+ </div>
4670+ <div id="recon-details-drawer" style="display:none;position:absolute;top:0;right:0;width:420px;height:100%;background:rgba(2,6,23,.98);border-left:1px solid var(--border);z-index:20;box-shadow:-12px 0 40px rgba(0,0,0,.45)">
4671+ <div style="display:flex;align-items:center;justify-content:space-between;padding:12px;border-bottom:1px solid var(--border)">
4672+ <div style="font-size:12px;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px">Finding Details</div>
4673+ <button id="recon-drawer-close" type="button" style="background:transparent;border:none;color:var(--text-muted);font-size:18px;cursor:pointer">✕</button>
4674+ </div>
4675+ <div id="recon-drawer-body" style="padding:12px;overflow:auto;height:calc(100% - 52px)"></div>
46274676 </div>
46284677 </section>
46294678 </div>
@@ -4632,10 +4681,19 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
46324681 const tabsEl = root . querySelector ( '#recon-left-rail' ) ;
46334682 const apkMetaBar = root . querySelector ( '#recon-apk-meta' ) ;
46344683 const filterBar = root . querySelector ( '#recon-filter-bar' ) ;
4684+ const chipBar = root . querySelector ( '#recon-quick-chips' ) ;
4685+ const viewModeSel = root . querySelector ( '#recon-view-mode' ) ;
4686+ const savedFiltersSel = root . querySelector ( '#recon-saved-filters' ) ;
4687+ const saveFilterBtn = root . querySelector ( '#recon-save-filter' ) ;
4688+ const deleteFilterBtn = root . querySelector ( '#recon-delete-filter' ) ;
4689+ const filterNameInput = root . querySelector ( '#recon-filter-name' ) ;
46354690 const standardView = root . querySelector ( '#recon-standard-view' ) ;
46364691 const assetsView = root . querySelector ( '#recon-assets-view' ) ;
46374692 const assetsContent = root . querySelector ( '#recon-assets-content' ) ;
46384693 const standardTable = root . querySelector ( '#recon-standard-view table.dashboard-table' ) ;
4694+ const drawer = root . querySelector ( '#recon-details-drawer' ) ;
4695+ const drawerBody = root . querySelector ( '#recon-drawer-body' ) ;
4696+ const drawerClose = root . querySelector ( '#recon-drawer-close' ) ;
46394697
46404698 if ( apkMetaBar ) {
46414699 if ( isAPKScan && apkPackageInfo ) {
@@ -4728,6 +4786,58 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
47284786 } ) ;
47294787 }
47304788
4789+ const renderSavedFilters = ( ) => {
4790+ if ( ! savedFiltersSel ) return ;
4791+ const names = Object . keys ( savedFilterSets ) . sort ( ) ;
4792+ savedFiltersSel . innerHTML = '<option value="">Saved filters…</option>' + names . map ( ( n ) => `<option value="${ esc ( n ) } ">${ esc ( n ) } </option>` ) . join ( '' ) ;
4793+ } ;
4794+ const readCurrentFilterSet = ( ) => ( {
4795+ activeKind,
4796+ searchHost,
4797+ searchTitle,
4798+ filterSeverity,
4799+ searchModule,
4800+ searchJsOnly,
4801+ quickChip,
4802+ presetMode,
4803+ } ) ;
4804+ const applyFilterSet = ( fs ) => {
4805+ if ( ! fs ) return ;
4806+ activeKind = fs . activeKind || activeKind ;
4807+ searchHost = String ( fs . searchHost || '' ) ;
4808+ searchTitle = String ( fs . searchTitle || '' ) ;
4809+ filterSeverity = String ( fs . filterSeverity || 'any' ) ;
4810+ searchModule = String ( fs . searchModule || 'all' ) ;
4811+ searchJsOnly = ! ! fs . searchJsOnly ;
4812+ quickChip = String ( fs . quickChip || 'none' ) ;
4813+ presetMode = String ( fs . presetMode || 'smart' ) ;
4814+ const hostInputEl = root . querySelector ( '#recon-filter-host' ) ;
4815+ const titleInputEl = root . querySelector ( '#recon-filter-title' ) ;
4816+ const severitySelEl = root . querySelector ( '#recon-filter-severity' ) ;
4817+ const jsChkEl = root . querySelector ( '#recon-filter-js-only' ) ;
4818+ if ( hostInputEl ) hostInputEl . value = searchHost ;
4819+ if ( titleInputEl ) titleInputEl . value = searchTitle ;
4820+ if ( severitySelEl ) severitySelEl . value = filterSeverity ;
4821+ if ( modSelect ) modSelect . value = searchModule ;
4822+ if ( jsChkEl ) jsChkEl . checked = searchJsOnly ;
4823+ if ( viewModeSel ) viewModeSel . value = presetMode ;
4824+ } ;
4825+
4826+ const chipDefs = [
4827+ { id : 'highplus' , label : 'High+' } ,
4828+ { id : 'hasurl' , label : 'Has URL' } ,
4829+ { id : 'exported' , label : 'Exported Components' } ,
4830+ { id : 'secrets' , label : 'Secrets' } ,
4831+ { id : 'onlyjs' , label : 'Only JS' } ,
4832+ ] ;
4833+ const renderChips = ( ) => {
4834+ if ( ! chipBar ) return ;
4835+ chipBar . innerHTML = chipDefs . map ( ( c ) => {
4836+ const active = quickChip === c . id ;
4837+ return `<button type="button" data-chip="${ escAttr ( c . id ) } " style="padding:5px 10px;border:1px solid ${ active ? 'rgba(34,211,238,.5)' : 'var(--border)' } ;border-radius:999px;background:${ active ? 'rgba(34,211,238,.13)' : 'rgba(255,255,255,.02)' } ;color:${ active ? 'var(--accent-cyan)' : 'var(--text-secondary)' } ;font-size:11px;cursor:pointer">${ esc ( c . label ) } </button>` ;
4838+ } ) . join ( '' ) ;
4839+ } ;
4840+
47314841 const renderTabs = ( ) => {
47324842 if ( ! tabsEl ) return ;
47334843 tabsEl . innerHTML = UNIQUE_TABS . map ( ( [ kind , label ] ) => {
@@ -4764,7 +4874,9 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
47644874 const cap = root . querySelector ( '#recon-unified-cap' ) ;
47654875 if ( shown ) shown . textContent = String ( filtered . length ) ;
47664876 if ( headRow ) {
4767- const cols = getUnifiedTableColumns ( activeKind ) ;
4877+ const cols = presetMode === 'raw'
4878+ ? [ 'TARGET' , 'SEV' , 'VULNERABILITY TYPE' , 'MODULE' ]
4879+ : getUnifiedTableColumns ( activeKind ) ;
47684880 headRow . innerHTML = `
47694881 <th style="width:36px;text-align:center;padding-left:10px">
47704882 <input type="checkbox" id="findings-select-all" title="Select all" style="width:14px;height:14px;accent-color:var(--accent-cyan);cursor:pointer">
@@ -4776,6 +4888,7 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
47764888 ` ;
47774889 }
47784890 if ( tbody ) {
4891+ currentRenderedRows = slice ;
47794892 tbody . innerHTML = slice . length ? slice . map ( ( r , idx ) => {
47804893 const sev = String ( r . severity || '' ) . toLowerCase ( ) . replace ( / [ — \- ] / g, '' ) . trim ( ) ;
47814894 const sevMeta = {
@@ -4788,8 +4901,14 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
47884901 } [ sev ] || { color : '#718096' , bg : '#71809615' , label : '—' } ;
47894902
47904903 const modInfo = getModuleDisplayInfo ( r . module ) ;
4904+ if ( presetMode === 'raw' ) return renderDefaultRow ( r , idx , modInfo , sevMeta ) ;
47914905 return renderRowForUnifiedTab ( r , idx , activeKind , modInfo , sevMeta ) ;
47924906 } ) . join ( '' ) : '<tr><td colspan="5" style="text-align:center;padding:28px;color:var(--text-muted);font-size:13px">No findings match the current filter.</td></tr>' ;
4907+ if ( slice . length ) {
4908+ Array . from ( tbody . querySelectorAll ( '.findings-row' ) ) . forEach ( ( tr , i ) => {
4909+ tr . setAttribute ( 'data-row-index' , String ( i ) ) ;
4910+ } ) ;
4911+ }
47934912 }
47944913 const pagContainer = root . querySelector ( '#recon-pagination' ) ;
47954914 if ( pagContainer ) {
@@ -4881,8 +5000,53 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
48815000 } ;
48825001
48835002 // Initial load
5003+ loadSavedSets ( ) ;
5004+ renderSavedFilters ( ) ;
5005+ renderChips ( ) ;
48845006 switchReconView ( activeKind ) ;
48855007
5008+ const renderDrawerRow = ( r ) => {
5009+ if ( ! drawerBody ) return ;
5010+ const safeJson = esc ( JSON . stringify ( r , null , 2 ) ) ;
5011+ drawerBody . innerHTML = `
5012+ <div style="display:grid;gap:10px">
5013+ <div><div style="font-size:11px;color:var(--text-muted)">Target</div><div style="font-family:var(--font-mono,monospace);font-size:12px;color:var(--text-primary);word-break:break-all">${ esc ( String ( r . target || r . host || '—' ) ) } </div></div>
5014+ <div><div style="font-size:11px;color:var(--text-muted)">Severity</div><div style="font-size:12px;color:var(--text-primary)">${ esc ( String ( r . severity || '—' ) ) } </div></div>
5015+ <div><div style="font-size:11px;color:var(--text-muted)">Module</div><div style="font-size:12px;color:var(--text-primary)">${ esc ( String ( r . module || '—' ) ) } </div></div>
5016+ <div><div style="font-size:11px;color:var(--text-muted)">Finding</div><div style="font-family:var(--font-mono,monospace);font-size:12px;color:var(--text-primary);word-break:break-word">${ esc ( String ( r . finding || '—' ) ) } </div></div>
5017+ <div><div style="font-size:11px;color:var(--text-muted)">Source File</div><div style="font-family:var(--font-mono,monospace);font-size:12px;color:var(--text-primary);word-break:break-all">${ esc ( String ( r . file || '—' ) ) } </div></div>
5018+ <div style="display:flex;gap:8px;flex-wrap:wrap;padding-top:4px">
5019+ <button id="drawer-copy-finding" type="button" style="padding:6px 10px;border:1px solid var(--border);border-radius:6px;background:rgba(34,211,238,.1);color:var(--accent-cyan);font-size:11px;cursor:pointer">Copy Finding</button>
5020+ <button id="drawer-copy-json" type="button" style="padding:6px 10px;border:1px solid var(--border);border-radius:6px;background:rgba(255,255,255,.03);color:var(--text-primary);font-size:11px;cursor:pointer">Copy JSON</button>
5021+ <button id="drawer-export-json" type="button" style="padding:6px 10px;border:1px solid var(--border);border-radius:6px;background:rgba(52,211,153,.1);color:#34d399;font-size:11px;cursor:pointer">Export JSON</button>
5022+ </div>
5023+ <pre style="margin:0;padding:10px;border:1px solid var(--border);border-radius:8px;background:rgba(255,255,255,.02);font-size:11px;line-height:1.45;color:var(--text-muted);overflow:auto;max-height:280px">${ safeJson } </pre>
5024+ </div>` ;
5025+ const copyFinding = drawerBody . querySelector ( '#drawer-copy-finding' ) ;
5026+ const copyJson = drawerBody . querySelector ( '#drawer-copy-json' ) ;
5027+ const exportJson = drawerBody . querySelector ( '#drawer-export-json' ) ;
5028+ if ( copyFinding ) copyFinding . onclick = async ( ) => { await copyToClipboard ( String ( r . finding || '' ) ) ; showToast ( 'success' , 'Copied' , 'Finding copied' ) ; } ;
5029+ if ( copyJson ) copyJson . onclick = async ( ) => { await copyToClipboard ( JSON . stringify ( r , null , 2 ) ) ; showToast ( 'success' , 'Copied' , 'JSON copied' ) ; } ;
5030+ if ( exportJson ) exportJson . onclick = ( ) => {
5031+ const blob = new Blob ( [ JSON . stringify ( r , null , 2 ) ] , { type : 'application/json' } ) ;
5032+ const url = URL . createObjectURL ( blob ) ;
5033+ const a = document . createElement ( 'a' ) ;
5034+ a . href = url ;
5035+ a . download = `finding-${ Date . now ( ) } .json` ;
5036+ document . body . appendChild ( a ) ;
5037+ a . click ( ) ;
5038+ a . remove ( ) ;
5039+ URL . revokeObjectURL ( url ) ;
5040+ } ;
5041+ } ;
5042+ const openDrawerForRow = ( r ) => {
5043+ if ( ! drawer ) return ;
5044+ renderDrawerRow ( r ) ;
5045+ drawer . style . display = 'block' ;
5046+ } ;
5047+ const closeDrawer = ( ) => { if ( drawer ) drawer . style . display = 'none' ; } ;
5048+ if ( drawerClose ) drawerClose . addEventListener ( 'click' , closeDrawer ) ;
5049+
48865050 // ── Draggable column widths ───────────────────────────────────────────────
48875051 const ensureColPixels = ( ) => {
48885052 const cg = root . querySelector ( '#recon-colgroup' ) ;
@@ -4934,6 +5098,57 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
49345098 const kind = tabBtn . getAttribute ( 'data-recon-kind' ) || 'all' ;
49355099 switchReconView ( kind ) ;
49365100 } ) ;
5101+ if ( chipBar ) {
5102+ chipBar . addEventListener ( 'click' , ( e ) => {
5103+ const btn = e . target . closest ( '[data-chip]' ) ;
5104+ if ( ! btn ) return ;
5105+ const id = String ( btn . getAttribute ( 'data-chip' ) || 'none' ) ;
5106+ quickChip = quickChip === id ? 'none' : id ;
5107+ renderChips ( ) ;
5108+ _currentPage = 1 ;
5109+ renderBody ( ) ;
5110+ } ) ;
5111+ }
5112+ if ( viewModeSel ) {
5113+ viewModeSel . addEventListener ( 'change' , ( ) => {
5114+ presetMode = String ( viewModeSel . value || 'smart' ) ;
5115+ _currentPage = 1 ;
5116+ renderBody ( ) ;
5117+ } ) ;
5118+ }
5119+ if ( saveFilterBtn ) {
5120+ saveFilterBtn . addEventListener ( 'click' , ( ) => {
5121+ const name = String ( filterNameInput ?. value || '' ) . trim ( ) ;
5122+ if ( ! name ) {
5123+ showToast ( 'error' , 'Missing name' , 'Enter a filter name first' ) ;
5124+ return ;
5125+ }
5126+ savedFilterSets [ name ] = readCurrentFilterSet ( ) ;
5127+ persistSavedSets ( ) ;
5128+ renderSavedFilters ( ) ;
5129+ if ( savedFiltersSel ) savedFiltersSel . value = name ;
5130+ showToast ( 'success' , 'Saved' , `Filter "${ name } " saved` ) ;
5131+ } ) ;
5132+ }
5133+ if ( deleteFilterBtn ) {
5134+ deleteFilterBtn . addEventListener ( 'click' , ( ) => {
5135+ const name = String ( savedFiltersSel ?. value || '' ) . trim ( ) ;
5136+ if ( ! name || ! savedFilterSets [ name ] ) return ;
5137+ delete savedFilterSets [ name ] ;
5138+ persistSavedSets ( ) ;
5139+ renderSavedFilters ( ) ;
5140+ showToast ( 'success' , 'Deleted' , `Filter "${ name } " deleted` ) ;
5141+ } ) ;
5142+ }
5143+ if ( savedFiltersSel ) {
5144+ savedFiltersSel . addEventListener ( 'change' , ( ) => {
5145+ const name = String ( savedFiltersSel . value || '' ) . trim ( ) ;
5146+ if ( ! name || ! savedFilterSets [ name ] ) return ;
5147+ applyFilterSet ( savedFilterSets [ name ] ) ;
5148+ renderChips ( ) ;
5149+ switchReconView ( activeKind ) ;
5150+ } ) ;
5151+ }
49375152 root . addEventListener ( 'mousedown' , ( e ) => {
49385153 const handle = e . target . closest ( '.col-resizer' ) ;
49395154 if ( ! handle || ! root . contains ( handle ) ) return ;
@@ -5036,13 +5251,14 @@ async function loadReconUnifiedTable(scanId, allFiles, containerId, scanRecord)
50365251 }
50375252 } ) ;
50385253
5039- // Clicking a row (not a link/checkbox) toggles its checkbox
5254+ // Clicking a row opens details drawer (selection stays checkbox-driven)
50405255 root . addEventListener ( 'click' , e => {
50415256 const row = e . target . closest ( '.findings-row' ) ;
50425257 if ( ! row ) return ;
5043- if ( e . target . tagName === 'A' || e . target . tagName === 'INPUT' ) return ;
5044- const chk = row . querySelector ( '.finding-chk' ) ;
5045- if ( chk ) { chk . checked = ! chk . checked ; _updateToolbar ( ) ; }
5258+ if ( e . target . tagName === 'A' || e . target . tagName === 'INPUT' || e . target . tagName === 'BUTTON' ) return ;
5259+ const idx = Number ( row . getAttribute ( 'data-row-index' ) || '-1' ) ;
5260+ const data = idx >= 0 ? currentRenderedRows [ idx ] : null ;
5261+ if ( data ) openDrawerForRow ( data ) ;
50465262 } ) ;
50475263
50485264 // Toolbar actions
0 commit comments