@@ -8,6 +8,15 @@ let sortColumn = null;
88let sortDirection = 'asc' ; // 'asc' or 'desc'
99let currentRoute = { view : 'list' , pathId : null } ; // Track current route
1010
11+ // Category tooltips
12+ const categoryTooltips = {
13+ 'self-escalation' : 'Modify your own permissions directly' ,
14+ 'lateral-movement' : 'Gain access to a different principal' ,
15+ 'service-passrole' : 'Pass a privileged role to a resource that executes code' ,
16+ 'access-resource' : 'Modify existing resources to gain elevated access' ,
17+ 'credential-access' : 'Read permissions that may expose credentials'
18+ } ;
19+
1120// DOM elements
1221const pathsContainer = document . getElementById ( 'paths-container' ) ;
1322const searchInput = document . getElementById ( 'search' ) ;
@@ -50,6 +59,7 @@ document.addEventListener('DOMContentLoaded', () => {
5059 initTheme ( ) ;
5160 setupEventListeners ( ) ;
5261 setupTabListeners ( ) ;
62+ setupInstantTooltips ( ) ; // Setup tooltips for detection circles and category chips
5363 loadPaths ( ) ; // This will call initRouter() when data is loaded
5464} ) ;
5565
@@ -554,7 +564,7 @@ function createPathCard(path) {
554564 <div class="path-card">
555565 <div class="path-card-header">
556566 <span class="path-id">${ path . id . toUpperCase ( ) } </span>
557- <span class="path-category ${ categoryClass } ">${ formatCategory ( path . category ) } </span>
567+ <span class="path-category ${ categoryClass } " data-category-tooltip=" ${ categoryTooltips [ path . category ] || '' } " >${ formatCategory ( path . category ) } </span>
558568 </div>
559569 <div class="path-name">${ escapeHtml ( path . name ) } </div>
560570 <div class="path-description">${ escapeHtml ( path . description ) } </div>
@@ -605,13 +615,13 @@ function createPathTable(paths) {
605615 </td>
606616 <td class="table-name">${ escapeHtml ( path . name ) } </td>
607617 <td class="table-category">
608- <span class="path-category ${ categoryClass } ">${ formatCategory ( path . category ) } </span>
618+ <span class="path-category ${ categoryClass } " data-category-tooltip=" ${ categoryTooltips [ path . category ] || '' } " >${ formatCategory ( path . category ) } </span>
609619 </td>
610620 <td class="table-detection">
611621 ${ detectionTools . length > 0 ?
612622 detectionTools . map ( tool => {
613623 const toolInfo = toolMetadata [ tool ] || { name : tool } ;
614- return `<span class="detection-tool-circle" title ="${ toolInfo . name || tool } " data-tool="${ tool } ">${ getToolInitials ( tool ) } </span>` ;
624+ return `<span class="detection-tool-circle" data-tool ="${ tool } " data-tool-name ="${ toolInfo . name || tool } ">${ getToolInitials ( tool ) } </span>` ;
615625 } ) . join ( '' )
616626 : '<span class="no-detection">—</span>' }
617627 </td>
@@ -623,6 +633,64 @@ function createPathTable(paths) {
623633 ` ;
624634}
625635
636+ // Setup instant tooltips for detection circles and category chips
637+ let tooltipsInitialized = false ;
638+ function setupInstantTooltips ( ) {
639+ // Only initialize once
640+ if ( tooltipsInitialized ) return ;
641+ tooltipsInitialized = true ;
642+
643+ // Create tooltip element (reuse single element)
644+ const tooltip = document . createElement ( 'div' ) ;
645+ tooltip . className = 'detection-tooltip' ;
646+ document . body . appendChild ( tooltip ) ;
647+
648+ // Use event delegation for hover on detection circles and category chips
649+ document . body . addEventListener ( 'mouseover' , ( e ) => {
650+ const circle = e . target . closest ( '.detection-tool-circle' ) ;
651+ const category = e . target . closest ( '.path-category' ) ;
652+
653+ if ( circle ) {
654+ const toolName = circle . getAttribute ( 'data-tool-name' ) ;
655+ if ( toolName ) {
656+ tooltip . textContent = toolName ;
657+
658+ // Position tooltip above the circle
659+ const rect = circle . getBoundingClientRect ( ) ;
660+ tooltip . style . left = `${ rect . left + rect . width / 2 } px` ;
661+ tooltip . style . top = `${ rect . top - 10 } px` ;
662+ tooltip . style . transform = 'translate(-50%, -100%)' ;
663+
664+ // Show immediately
665+ tooltip . classList . add ( 'visible' ) ;
666+ }
667+ } else if ( category ) {
668+ const categoryTooltip = category . getAttribute ( 'data-category-tooltip' ) ;
669+ if ( categoryTooltip ) {
670+ tooltip . textContent = categoryTooltip ;
671+
672+ // Position tooltip above the category chip
673+ const rect = category . getBoundingClientRect ( ) ;
674+ tooltip . style . left = `${ rect . left + rect . width / 2 } px` ;
675+ tooltip . style . top = `${ rect . top - 10 } px` ;
676+ tooltip . style . transform = 'translate(-50%, -100%)' ;
677+
678+ // Show immediately
679+ tooltip . classList . add ( 'visible' ) ;
680+ }
681+ }
682+ } ) ;
683+
684+ document . body . addEventListener ( 'mouseout' , ( e ) => {
685+ const circle = e . target . closest ( '.detection-tool-circle' ) ;
686+ const category = e . target . closest ( '.path-category' ) ;
687+
688+ if ( circle || category ) {
689+ tooltip . classList . remove ( 'visible' ) ;
690+ }
691+ } ) ;
692+ }
693+
626694// Handle path click with modifier keys
627695function handlePathClick ( event , path ) {
628696 // Check if Ctrl (Windows/Linux) or Cmd (Mac) is pressed, or if it's a middle-click
@@ -684,7 +752,7 @@ function showPathDetails(path) {
684752 </div>
685753 <div class="metadata-item">
686754 <span class="metadata-label">Category:</span>
687- <span class="path-category category-${ path . category } ">${ formatCategory ( path . category ) } </span>
755+ <span class="path-category category-${ path . category } " data-category-tooltip=" ${ categoryTooltips [ path . category ] || '' } " >${ formatCategory ( path . category ) } </span>
688756 </div>
689757 </div>
690758
0 commit comments